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

tenthe 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 f5c2367633 1357 harmonize resource creation workflow for pipelines 
(#4708)
f5c2367633 is described below

commit f5c23676336a67df882162a2c949e1b37d56fcb4
Author: Jacqueline Höllig <[email protected]>
AuthorDate: Thu Jul 23 18:08:03 2026 +0200

    1357 harmonize resource creation workflow for pipelines (#4708)
    
    Co-authored-by: Philipp Zehnder <[email protected]>
---
 ui/cypress/support/utils/pipeline/PipelineBtns.ts  |  39 +-
 ui/cypress/support/utils/pipeline/PipelineUtils.ts |  65 ++-
 .../editAdapterSettingsAndPipeline.smoke.spec.ts   |   4 +-
 ui/cypress/tests/pipeline/pipelineAsset.spec.ts    |  58 +--
 .../pipeline/pipelineDatasetSchemaUpdate.spec.ts   |   6 +-
 .../tests/pipeline/renamePipelineTest.spec.ts      |  19 +-
 .../pipeline/updatePipelineTest.smoke.spec.ts      |  14 +-
 ui/deployment/i18n/de.json                         |  22 +-
 ui/deployment/i18n/en.json                         |  14 +-
 ui/deployment/i18n/pl.json                         |  20 +-
 .../pipeline-started-status.component.html         |   2 +-
 .../pipeline-assembly-options.component.html       |  51 ++-
 .../pipeline-assembly-options.component.ts         |  42 +-
 .../pipeline-assembly.component.html               |   5 +-
 .../pipeline-assembly.component.ts                 | 312 +++++++++++++-
 .../save-pipeline-settings.component.html          | 177 --------
 .../save-pipeline-settings.component.scss          |  21 -
 .../save-pipeline-settings.component.ts            | 181 --------
 .../save-pipeline/save-pipeline.component.html     | 118 ++---
 .../save-pipeline/save-pipeline.component.scss     |  18 -
 .../save-pipeline/save-pipeline.component.ts       | 476 +++++++--------------
 ui/src/app/editor/editor.component.html            |   1 +
 ui/src/app/editor/editor.component.ts              |   5 +-
 .../pipeline-overview.component.html               |  26 +-
 .../services/pipeline-operations.service.ts        | 127 +++++-
 25 files changed, 884 insertions(+), 939 deletions(-)

diff --git a/ui/cypress/support/utils/pipeline/PipelineBtns.ts 
b/ui/cypress/support/utils/pipeline/PipelineBtns.ts
index 833c1b170b..70c4bbd2f6 100644
--- a/ui/cypress/support/utils/pipeline/PipelineBtns.ts
+++ b/ui/cypress/support/utils/pipeline/PipelineBtns.ts
@@ -32,6 +32,22 @@ export class PipelineBtns {
         return cy.dataCy('modify-pipeline-btn');
     }
 
+    public static clonePipeline() {
+        return cy.dataCy('clone-pipeline-btn');
+    }
+
+    public static managePipeline() {
+        return cy.dataCy('open-manage-pipeline');
+    }
+
+    public static pipelineOptions() {
+        return cy.dataCy('options-pipeline');
+    }
+
+    public static managePipelineInEditor() {
+        return cy.dataCy('manage-pipeline-btn');
+    }
+
     public static pipelinesToEditor() {
         return cy.dataCy('pipelines-navigate-to-editor');
     }
@@ -83,27 +99,35 @@ export class PipelineBtns {
     }
 
     public static savePipelineBtn() {
-        return cy.dataCy('sp-editor-save-pipeline');
+        return cy
+            .get('sp-split-button[datacy="sp-editor-save-pipeline"]', {
+                timeout: 15000,
+            })
+            .find('.split-button__main');
     }
 
     public static pipelineCloneModeBtn() {
         return cy.dataCy('pipeline-update-mode-clone');
     }
 
-    public static navigateToOverviewCheckbox() {
-        return cy.dataCy('sp-editor-checkbox-navigate-to-overview');
+    public static managedResourceName() {
+        return cy.dataCy('managed-resource-name');
     }
 
     public static editorApplyBtn() {
         return cy.dataCy('sp-editor-apply');
     }
 
+    public static editorSaveBtn() {
+        return cy.dataCy('sp-manage-save');
+    }
+
     public static pipelineStartedError() {
         return cy.dataCy('sp-pipeline-started-error', { timeout: 15000 });
     }
 
     public static pipelineStartedSuccess() {
-        return cy.dataCy('sp-pipeline-started-success', { timeout: 15000 });
+        return cy.dataCy('sp-pipeline-started', { timeout: 15000 });
     }
 
     public static updateAndMigratePipeline() {
@@ -159,11 +183,14 @@ export class PipelineBtns {
         return cy.dataCy('settings-pipeline-element-button');
     }
     public static pipelineEditorSave() {
-        return cy.dataCy('sp-editor-save-pipeline');
+        return PipelineBtns.savePipelineBtn();
     }
 
+    public static savePipelineStatusClose() {
+        return cy.dataCy('sp-save-pipeline-status-close');
+    }
     public static pipelineAssetCheckbox() {
-        return cy.dataCy('sp-show-pipeline-asset-checkbox');
+        return cy.dataCy('sp-show-asset-checkbox');
     }
 
     public static pipelineEditorCancel() {
diff --git a/ui/cypress/support/utils/pipeline/PipelineUtils.ts 
b/ui/cypress/support/utils/pipeline/PipelineUtils.ts
index a5b72746c0..6d4b65bab2 100644
--- a/ui/cypress/support/utils/pipeline/PipelineUtils.ts
+++ b/ui/cypress/support/utils/pipeline/PipelineUtils.ts
@@ -157,9 +157,7 @@ export class PipelineUtils {
         // Save and start pipeline
         PipelineBtns.savePipelineBtn().click();
         if (pipelineInput) {
-            cy.dataCy('sp-editor-pipeline-name').type(
-                pipelineInput.pipelineName,
-            );
+            
PipelineBtns.managedResourceName().type(pipelineInput.pipelineName);
         }
         PipelineUtils.finalizePipelineStart();
     }
@@ -171,15 +169,13 @@ export class PipelineUtils {
         // Save and start pipeline
         PipelineBtns.savePipelineBtn().click();
         if (pipelineInput) {
-            cy.dataCy('sp-editor-pipeline-name').type(
-                pipelineInput.pipelineName,
-            );
+            
PipelineBtns.managedResourceName().type(pipelineInput.pipelineName);
         }
         PipelineUtils.finalizePipelineStart(assetNameList);
     }
 
     private static addToAsset(assetNameList) {
-        cy.dataCy('sp-show-pipeline-asset-checkbox')
+        PipelineBtns.pipelineAssetCheckbox()
             .find('input[type="checkbox"]')
             .then($checkbox => {
                 if (!$checkbox.prop('checked')) {
@@ -197,27 +193,64 @@ export class PipelineUtils {
         });
     }
 
-    public static clonePipeline(newPipelineName: string) {
-        cy.dataCy('sp-editor-pipeline-name').type(newPipelineName);
-        PipelineBtns.pipelineCloneModeBtn().children().click();
+    public static addManagedPipelineToAssets(assetNameList: string[]) {
+        PipelineUtils.addToAsset(assetNameList);
+    }
+
+    public static clonePipeline(
+        pipelineName: string,
+        newPipelineName?: string,
+    ) {
+        GeneralUtils.openMenuForRow(pipelineName);
+        PipelineBtns.clonePipeline().first().click();
+        PipelineBtns.savePipelineBtn().should('be.visible').click();
+
+        if (newPipelineName) {
+            PipelineBtns.managedResourceName()
+                .should('be.visible')
+                .clear()
+                .type(newPipelineName);
+        }
     }
 
     public static updatePipeline(newPipelineName: string) {
-        //PipelineBtns.pipelineCloneModeBtn().children().click();
-        cy.dataCy('sp-editor-pipeline-name').type(newPipelineName);
+        PipelineBtns.managedResourceName().type(newPipelineName);
+    }
+
+    public static openPipelineManagementInEditor() {
+        PipelineBtns.pipelineOptions().click();
+        PipelineBtns.managePipelineInEditor().click();
+    }
+
+    public static renameManagedPipeline(newPipelineName: string) {
+        PipelineBtns.managedResourceName().clear();
+        PipelineUtils.updatePipeline(newPipelineName);
+    }
+
+    public static applyPipelineManagementChanges() {
+        PipelineBtns.editorSaveBtn().click();
     }
 
     public static finalizePipelineStart(assetNameList?: string[]) {
-        PipelineBtns.navigateToOverviewCheckbox().children().click();
         if (assetNameList) {
             PipelineUtils.addToAsset(assetNameList);
         }
-        PipelineBtns.editorApplyBtn().click();
 
-        cy.dataCy('sp-pipeline-started-success', { timeout: 15000 }).should(
+        PipelineUtils.applyPipelineManagementChanges();
+        PipelineUtils.closePipelineSaveStatus();
+    }
+
+    public static savePipelineUpdate() {
+        PipelineBtns.savePipelineBtn().click();
+        PipelineUtils.closePipelineSaveStatus();
+        PipelineBtns.pipelinesToEditor().should('exist');
+    }
+
+    private static closePipelineSaveStatus() {
+        cy.dataCy('sp-pipeline-started', { timeout: 15000 }).should(
             'be.visible',
         );
-        PipelineBtns.navigateToPipelineOverview().click();
+        PipelineBtns.savePipelineStatusClose().click();
     }
 
     public static checkAmountOfPipelinesPipeline(amount: number) {
diff --git 
a/ui/cypress/tests/connect/editAdapterSettingsAndPipeline.smoke.spec.ts 
b/ui/cypress/tests/connect/editAdapterSettingsAndPipeline.smoke.spec.ts
index b1ea22176a..40d5231981 100644
--- a/ui/cypress/tests/connect/editAdapterSettingsAndPipeline.smoke.spec.ts
+++ b/ui/cypress/tests/connect/editAdapterSettingsAndPipeline.smoke.spec.ts
@@ -99,9 +99,7 @@ describe('Test Edit Adapter and Pipeline', () => {
             .click({ force: true });
         PipelineBtns.saveElementConfigBtn().click({ force: true });
         PipelineBtns.savePipelineBtn().click();
-        PipelineBtns.navigateToOverviewCheckbox().children().click();
-        PipelineBtns.editorApplyBtn().click();
-        PipelineBtns.navigateToPipelineOverview().click();
+        PipelineBtns.savePipelineStatusClose().click();
 
         // Visit dashboard
         cy.wait(5000);
diff --git a/ui/cypress/tests/pipeline/pipelineAsset.spec.ts 
b/ui/cypress/tests/pipeline/pipelineAsset.spec.ts
index f9aa31fb11..31b45d7913 100644
--- a/ui/cypress/tests/pipeline/pipelineAsset.spec.ts
+++ b/ui/cypress/tests/pipeline/pipelineAsset.spec.ts
@@ -27,6 +27,10 @@ describe('Test Saving Pipeline with Asset Link', () => {
     const assetName1 = 'Test1';
     const assetName2 = 'Test2';
     const assetName3 = 'Test3';
+    const initialPipelineName = 'Pipeline Test';
+    const renamedPipelineName = 'Renamed Pipeline';
+    const linkedPipelineResources = 1;
+
     beforeEach('Setup Test', () => {
         cy.initStreamPipesTest();
         AssetUtils.goToAssets();
@@ -42,7 +46,7 @@ describe('Test Saving Pipeline with Asset Link', () => {
 
         ConnectUtils.addMachineDataSimulator(adapterName);
 
-        const pipelineInput = PipelineBuilder.create('Pipeline Test')
+        const pipelineInput = PipelineBuilder.create(initialPipelineName)
             .addSource(adapterName)
             .addSink(
                 PipelineElementBuilder.create('data_lake')
@@ -55,44 +59,44 @@ describe('Test Saving Pipeline with Asset Link', () => {
             assetName1,
             assetName2,
         ]);
+        PipelineUtils.goToPipelines();
     });
 
     it('Add Pipeline to Asset during creation', () => {
-        PipelineUtils.editPipeline('Pipeline Test');
-
-        // Go Back to Asset
         AssetUtils.goToAssets();
-        AssetUtils.checkAmountOfAssetsGreaterThan(0);
-
-        // CLick on Asset
+        AssetUtils.checkAmountOfLinkedResourcesByAssetName(
+            assetName1,
+            linkedPipelineResources,
+        );
+        AssetUtils.checkAmountOfLinkedResourcesByAssetName(
+            assetName2,
+            linkedPipelineResources,
+        );
+    });
 
-        AssetUtils.editAsset(assetName1);
-        AssetUtils.checkAmountOfLinkedResources(2);
+    it('Edit Pipeline to Asset during Edit', () => {
+        PipelineUtils.editPipeline(initialPipelineName);
+        PipelineUtils.openPipelineManagementInEditor();
+        PipelineUtils.renameManagedPipeline(renamedPipelineName);
+        PipelineUtils.addManagedPipelineToAssets([assetName3]);
+        PipelineUtils.applyPipelineManagementChanges();
+        PipelineUtils.savePipelineUpdate();
 
-        // Go Back to Asset
         AssetUtils.goToAssets();
-        AssetUtils.checkAmountOfAssetsGreaterThan(0);
-        AssetUtils.editAsset(assetName2);
-        AssetUtils.checkAmountOfLinkedResources(2);
-    });
 
-    it('Edit Pipeline to Asset during Edit', () => {
-        PipelineUtils.editPipeline('Pipeline Test');
-        cy.dataCy('sp-editor-save-pipeline', { timeout: 10000 })
-            .should('exist')
-            .click();
-        cy.dataCy('sp-editor-pipeline-name').clear();
-        PipelineUtils.updatePipeline('Renamed Pipeline');
-        PipelineUtils.finalizePipelineStart([assetName1, assetName3]);
+        AssetUtils.checkAmountOfLinkedResourcesByAssetName(
+            assetName2,
+            linkedPipelineResources,
+        );
 
-        // Test Number of Asset Links
-        AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName2, 2);
-        AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName3, 2);
+        AssetUtils.checkAmountOfLinkedResourcesByAssetName(
+            assetName3,
+            linkedPipelineResources,
+        );
 
-        // Test Renaming
         AssetUtils.checkResourceNamingByAssetName(
             assetName2,
-            'Renamed Pipeline',
+            renamedPipelineName,
         );
     });
 });
diff --git a/ui/cypress/tests/pipeline/pipelineDatasetSchemaUpdate.spec.ts 
b/ui/cypress/tests/pipeline/pipelineDatasetSchemaUpdate.spec.ts
index de7a007b0c..9edf87fb0e 100644
--- a/ui/cypress/tests/pipeline/pipelineDatasetSchemaUpdate.spec.ts
+++ b/ui/cypress/tests/pipeline/pipelineDatasetSchemaUpdate.spec.ts
@@ -81,8 +81,7 @@ describe('Test pipeline updates with data lake schema 
changes', () => {
         PipelineBtns.updateAndMigratePipeline().should('not.be.disabled');
         PipelineBtns.updateAndMigratePipeline().click();
 
-        PipelineBtns.pipelineStartedSuccess().should('be.visible');
-        PipelineBtns.navigateToPipelineOverview().click();
+        PipelineUtils.closePipelineSaveStatus();
 
         ChartUtils.goToDatalake();
         ChartBtns.chartSyncProblemIcon().should('be.visible');
@@ -109,7 +108,6 @@ describe('Test pipeline updates with data lake schema 
changes', () => {
 
         PipelineBtns.pipelineEditWarning().should('not.exist');
         PipelineBtns.pipelineStartedSuccess().should('be.visible');
-        PipelineBtns.navigateToPipelineOverview().click();
 
         ChartUtils.goToDatalake();
         ChartBtns.chartSyncProblemIcon().should('not.exist');
@@ -164,8 +162,6 @@ describe('Test pipeline updates with data lake schema 
changes', () => {
 
     function savePipeline() {
         PipelineBtns.savePipelineBtn().click();
-        PipelineBtns.navigateToOverviewCheckbox().children().click();
-        PipelineBtns.editorApplyBtn().click();
     }
 
     function addTableChart(measurementName: string) {
diff --git a/ui/cypress/tests/pipeline/renamePipelineTest.spec.ts 
b/ui/cypress/tests/pipeline/renamePipelineTest.spec.ts
index e57a295947..67acfb952c 100644
--- a/ui/cypress/tests/pipeline/renamePipelineTest.spec.ts
+++ b/ui/cypress/tests/pipeline/renamePipelineTest.spec.ts
@@ -16,6 +16,7 @@
  *
  */
 
+import { GeneralUtils } from '../../support/utils/GeneralUtils';
 import { PipelineBtns } from '../../support/utils/pipeline/PipelineBtns';
 import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
 
@@ -30,20 +31,20 @@ describe('Test rename of running pipeline', () => {
         PipelineUtils.verifyPipelineCount(1);
         PipelineUtils.verifyPipelineName('Pipeline Test');
 
-        PipelineUtils.editPipeline('Pipeline Test');
+        GeneralUtils.openMenuForRow('Pipeline Test');
         cy.wait(1000);
-        PipelineBtns.savePipelineBtn().click();
-        cy.dataCy('sp-editor-pipeline-name').clear();
-        PipelineUtils.updatePipeline('Renamed Pipeline');
-        PipelineUtils.finalizePipelineStart();
+
+        PipelineBtns.managePipeline().click();
+        PipelineUtils.renameManagedPipeline('Renamed Pipeline');
+        PipelineBtns.editorSaveBtn().click();
 
         PipelineUtils.verifyPipelineCount(1);
         PipelineUtils.verifyPipelineName('Renamed Pipeline');
 
-        PipelineUtils.editPipeline('Renamed Pipeline');
-        PipelineBtns.savePipelineBtn().click();
-        cy.dataCy('sp-editor-pipeline-name').clear();
-        PipelineUtils.clonePipeline('Cloned Renamed Pipeline');
+        PipelineUtils.clonePipeline(
+            'Renamed Pipeline',
+            'Cloned Renamed Pipeline',
+        );
         PipelineUtils.finalizePipelineStart();
 
         PipelineUtils.verifyPipelineCount(2);
diff --git a/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts 
b/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
index d31c0d04f5..7bcaeec675 100644
--- a/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
+++ b/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
@@ -29,14 +29,16 @@ describe('Test update of running pipeline', () => {
         PipelineUtils.addSampleAdapterAndPipeline();
         PipelineUtils.editPipeline(pipelineName);
         cy.wait(1000);
-        PipelineUtils.startPipeline();
+        PipelineBtns.savePipelineBtn().click();
+        PipelineBtns.savePipelineStatusClose().click();
         cy.dataCy('more-options', { timeout: 10000 }).should('have.length', 1);
 
-        PipelineUtils.editPipeline(pipelineName);
-        cy.wait(1000);
-        PipelineBtns.savePipelineBtn().click();
-        PipelineUtils.clonePipeline('Pipeline Test 2');
-        PipelineUtils.finalizePipelineStart();
+        PipelineUtils.clonePipeline(pipelineName, 'Pipeline Test 2');
+        PipelineBtns.editorSaveBtn().click();
+        cy.dataCy('sp-pipeline-started', { timeout: 15000 }).should(
+            'be.visible',
+        );
+        PipelineBtns.savePipelineStatusClose().click();
         cy.dataCy('more-options', { timeout: 10000 }).should('have.length', 2);
     });
 });
diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json
index 726f1dbeff..76a652ae74 100644
--- a/ui/deployment/i18n/de.json
+++ b/ui/deployment/i18n/de.json
@@ -57,7 +57,6 @@
   "Add Adapter to an existing Asset": "Adapter zu einem bestehenden Asset 
hinzufügen",
   "Add Filter": "Filter hinzufügen",
   "Add Mapping": "Mapping hinzufügen",
-  "Add Pipeline to Assets": "Pipeline zu Assets hinzufügen",
   "Add additional fields to the asset, e.g., to manage responsibilities": 
"Hinzufügen zusätzlicher Felder zum Asset, z. B. zur Verwaltung von 
Zuständigkeiten",
   "Add all direct children": "Alle direkten Unterknoten hinzufügen",
   "Add an additional link that links to your support page": "Fügen Sie einen 
zusätzlichen Link hinzu, der zu Ihrer Support-Seite führt",
@@ -184,7 +183,7 @@
   "Charts": "Diagramme",
   "Check that the uploaded zip file is a valid export": "Prüfen Sie, ob die 
hochgeladene Zip-Datei ein gültiger Export ist",
   "Checking migrations for adapter {{adapterName}}": "Migrationen für Adapter 
{{adapterName}} prüfen",
-  "Checking pipeline update": "Überprüfe Pipeline-Update",
+  "Checking pipeline update": "Pipeline-Aktualisierung prüfen",
   "Choose a name for your site": "Name des Standorts festlegen",
   "Choose existing file": "Vorhandene Datei auswählen",
   "Choose target dataset": "Ziel-Dataset auswählen",
@@ -250,7 +249,6 @@
   "Create new API key": "Neuen API-Schlüssel erstellen",
   "Create new account": "Neuen Account erstellen",
   "Create new dataset": "Neues Dataset erstellen",
-  "Create new pipeline": "Neue Pipeline erstellen",
   "Create new source": "Neue Quelle erstellen",
   "Create template": "Vorlage erstellen",
   "Create transformation template": "Skriptvorlage erstellen",
@@ -650,6 +648,7 @@
   "Manage Chart ": "Diagramm verwalten",
   "Manage Dashboard ": "Dashboard verwalten",
   "Manage Labels": "Labels verwalten",
+  "Manage Pipeline ": "Pipeline verwalten ",
   "Manage Sites": "Standorte verwalten",
   "Manage asset links": "Verwalten von Asset-Verknüpfungen",
   "Manage chart": "Diagramm verwalten",
@@ -660,6 +659,7 @@
   "Manage permissions for adapter ": "Berechtigungen für Adapter verwalten",
   "Manage permissions for dataset ": "Berechtigungen für Datensatz verwalten",
   "Manage permissions for pipeline element {{name}}": "Berechtigungen für 
Pipeline-Element {{name}} verwalten",
+  "Manage pipeline": "Pipeline verwalten",
   "Manage roles": "Rollen verwalten",
   "Manage site": "Standorte verwalten",
   "Manage user groups": "Benutzergruppen verwalten",
@@ -705,7 +705,6 @@
   "Name + Value + Percent": "Name + Wert + Prozent",
   "Name used for the grouped remaining slices.": "Name, der für die 
gruppierten übrigen Segmente verwendet wird.",
   "Navigate": "navigieren.",
-  "Navigate to pipeline overview afterwards": "Navigieren Sie anschließend zur 
Pipeline-Übersicht",
   "Nested": "Verschachtelt",
   "Network Error": "Netzwerkfehler",
   "New": "Neu",
@@ -797,15 +796,9 @@
   "Pie": "Kreisdiagramm",
   "Pin": "Pin",
   "Pipeline": "Pipeline",
-  "Pipeline Name": "Name der Pipeline",
   "Pipeline as code": "Pipeline als Code",
-  "Pipeline description must not have more than 80 characters.": "Die 
Pipeline-Beschreibung darf nicht mehr als 80 Zeichen umfassen.",
   "Pipeline elements": "Pipeline-Elemente",
   "Pipeline health monitoring discovered the following issues:": "Die 
Zustandsüberwachung der Pipeline hat folgende Probleme festgestellt:",
-  "Pipeline name can only contain letters, numbers, dashes (-), and 
underscores (_).": "Pipeline-Namen dürfen nur Buchstaben, Zahlen, Bindestriche 
(-) und Unterstriche (_) enthalten.",
-  "Pipeline name cannot start or end with a space.": "Der Name der Pipeline 
darf nicht mit einem Leerzeichen beginnen oder enden.",
-  "Pipeline name is required.": "Der Name der Pipeline ist erforderlich.",
-  "Pipeline name must have between 3 and 50 characters.": "Der Name der 
Pipeline muss zwischen 3 und 50 Zeichen haben.",
   "Pipelines": "Pipelines",
   "Places labels inside slices or outside the chart.": "Platziert 
Beschriftungen innerhalb der Segmente oder außerhalb des Diagramms.",
   "Please change the adapter configuration to fix them.": "Bitte ändern Sie 
die Adapterkonfiguration, um sie zu beheben.",
@@ -942,8 +935,8 @@
   "Save in a new data lake": "In einem neuen Data Lake speichern",
   "Save pipeline": "Pipeline speichern",
   "Save template": "Vorlage speichern",
-  "Saving metadata": "Speichern von Metadaten",
-  "Saving pipeline": "Speichern der Pipeline",
+  "Saving metadata": "Metadaten speichern",
+  "Saving pipeline": "Pipeline speichern",
   "Saving pipeline modifications": "Änderungen werden gespeichert",
   "Scatter": "Streudiagramm",
   "Scattered Line": "Streudiagramm-Linie",
@@ -1020,7 +1013,6 @@
   "Show latest time above card": "Neueste Zeit über der Karte anzeigen",
   "Show legend": "Legende anzeigen",
   "Show only recommended settings": "Nur empfohlene Einstellungen anzeigen",
-  "Show pipeline configuration as code": "Pipeline-Konfiguration als Code 
anzeigen",
   "Show progress label": "Fortschrittsbeschriftung anzeigen",
   "Show raw data from your data source.": "Zeigen Sie Rohdaten aus Ihrer 
Datenquelle an.",
   "Show single data entry.": "Einzelne Dateneinträge anzeigen.",
@@ -1063,12 +1055,11 @@
   "Start export process": "Exportvorgang starten",
   "Start import process": "Importvorgang starten",
   "Start pipeline": "Pipeline starten",
-  "Start pipeline immediately": "Pipeline sofort starten",
   "Start selected adapters": "Ausgewählte Adapter starten",
   "Starting": "Starte",
   "Starting adapter": "Adapter wird gestartet",
   "Starting adapter {{adapterName}}": "Adapter starten {{adapterName}}",
-  "Starting pipeline": "Start der Pipeline",
+  "Starting pipeline": "Pipeline starten",
   "Starting pipeline ": "Starte Pipeline",
   "State": "Status",
   "Status": "Status",
@@ -1083,6 +1074,7 @@
   "Stopping pipeline": "Pipeline stoppen",
   "Stopping pipeline...": "Pipeline stoppen...",
   "Store all events of this source in the internal data store": "Speichern 
aller Daten dieser Quelle im internen DataLake",
+  "Store and Start": "Speichern und Starten",
   "Store as template": "Als Vorlage speichern",
   "Style/Tile server URL": "Stil/Tile-Server URL",
   "Success": "Erfolg",
diff --git a/ui/deployment/i18n/en.json b/ui/deployment/i18n/en.json
index 073f3b8872..f0b7ebadf8 100644
--- a/ui/deployment/i18n/en.json
+++ b/ui/deployment/i18n/en.json
@@ -57,7 +57,6 @@
   "Add Adapter to an existing Asset": null,
   "Add Filter": null,
   "Add Mapping": null,
-  "Add Pipeline to Assets": null,
   "Add additional fields to the asset, e.g., to manage responsibilities": null,
   "Add all direct children": null,
   "Add an additional link that links to your support page": null,
@@ -250,7 +249,6 @@
   "Create new API key": null,
   "Create new account": null,
   "Create new dataset": null,
-  "Create new pipeline": null,
   "Create new source": null,
   "Create template": null,
   "Create transformation template": null,
@@ -650,6 +648,7 @@
   "Manage Chart ": null,
   "Manage Dashboard ": null,
   "Manage Labels": null,
+  "Manage Pipeline ": null,
   "Manage Sites": null,
   "Manage asset links": null,
   "Manage chart": null,
@@ -660,6 +659,7 @@
   "Manage permissions for adapter ": null,
   "Manage permissions for dataset ": null,
   "Manage permissions for pipeline element {{name}}": "Manage permissions for 
pipeline element {{name}}",
+  "Manage pipeline": null,
   "Manage roles": null,
   "Manage site": null,
   "Manage user groups": null,
@@ -705,7 +705,6 @@
   "Name + Value + Percent": null,
   "Name used for the grouped remaining slices.": null,
   "Navigate": null,
-  "Navigate to pipeline overview afterwards": null,
   "Nested": null,
   "Network Error": null,
   "New": null,
@@ -797,15 +796,9 @@
   "Pie": null,
   "Pin": null,
   "Pipeline": null,
-  "Pipeline Name": null,
   "Pipeline as code": null,
-  "Pipeline description must not have more than 80 characters.": null,
   "Pipeline elements": null,
   "Pipeline health monitoring discovered the following issues:": null,
-  "Pipeline name can only contain letters, numbers, dashes (-), and 
underscores (_).": null,
-  "Pipeline name cannot start or end with a space.": null,
-  "Pipeline name is required.": null,
-  "Pipeline name must have between 3 and 50 characters.": null,
   "Pipelines": null,
   "Places labels inside slices or outside the chart.": null,
   "Please change the adapter configuration to fix them.": null,
@@ -1020,7 +1013,6 @@
   "Show latest time above card": null,
   "Show legend": null,
   "Show only recommended settings": null,
-  "Show pipeline configuration as code": null,
   "Show progress label": null,
   "Show raw data from your data source.": null,
   "Show single data entry.": null,
@@ -1063,7 +1055,6 @@
   "Start export process": null,
   "Start import process": null,
   "Start pipeline": null,
-  "Start pipeline immediately": null,
   "Start selected adapters": null,
   "Starting": null,
   "Starting adapter": null,
@@ -1083,6 +1074,7 @@
   "Stopping pipeline": null,
   "Stopping pipeline...": null,
   "Store all events of this source in the internal data store": null,
+  "Store and Start": null,
   "Store as template": null,
   "Style/Tile server URL": null,
   "Success": null,
diff --git a/ui/deployment/i18n/pl.json b/ui/deployment/i18n/pl.json
index 0764b3d447..dab204a19c 100644
--- a/ui/deployment/i18n/pl.json
+++ b/ui/deployment/i18n/pl.json
@@ -57,7 +57,6 @@
   "Add Adapter to an existing Asset": "Dodaj adapter do istniejącego zasobu",
   "Add Filter": "Dodaj filtr",
   "Add Mapping": "Dodaj mapowanie",
-  "Add Pipeline to Assets": "Dodaj strumień do zasobów",
   "Add additional fields to the asset, e.g., to manage responsibilities": 
"Dodaj dodatkowe pola do zasobu, np. do zarządzania odpowiedzialnościami",
   "Add all direct children": "Dodaj wszystkie bezpośrednie elementy potomne",
   "Add an additional link that links to your support page": "Dodaj dodatkowy 
link do strony wsparcia",
@@ -184,7 +183,7 @@
   "Charts": "Wykresy",
   "Check that the uploaded zip file is a valid export": "Sprawdź, czy 
przesłany plik ZIP jest prawidłowym eksportem",
   "Checking migrations for adapter {{adapterName}}": "Sprawdzanie migracji dla 
adaptera {{adapterName}}",
-  "Checking pipeline update": "Sprawdzanie aktualizacji strumienia",
+  "Checking pipeline update": "Sprawdzanie aktualizacji potoku",
   "Choose a name for your site": "Wybierz nazwę dla swojej lokalizacji",
   "Choose existing file": "Wybierz istniejący plik",
   "Choose target dataset": "Wybierz docelowy zbiór danych",
@@ -250,7 +249,6 @@
   "Create new API key": "Utwórz nowy klucz API",
   "Create new account": "Utwórz nowe konto",
   "Create new dataset": "Utwórz nowy zbiór danych",
-  "Create new pipeline": "Utwórz nowy strumień",
   "Create new source": "Utwórz nowe źródło",
   "Create template": "Utwórz szablon",
   "Create transformation template": "Utwórz szablon transformacji",
@@ -650,6 +648,7 @@
   "Manage Chart ": "Zarządzaj wykresem",
   "Manage Dashboard ": "Zarządzaj pulpitem nawigacyjnym",
   "Manage Labels": "Zarządzaj etykietami",
+  "Manage Pipeline ": "Zarządzaj procesem ",
   "Manage Sites": "Zarządzaj lokalizacjami",
   "Manage asset links": "Zarządzaj linkami zasobu",
   "Manage chart": "Zarządzanie wykresem",
@@ -660,6 +659,7 @@
   "Manage permissions for adapter ": "Zarządzaj uprawnieniami dla adaptera ",
   "Manage permissions for dataset ": "Zarządzaj uprawnieniami dla zbioru 
danych ",
   "Manage permissions for pipeline element {{name}}": "Zarządzaj uprawnieniami 
dla elementu strumienia {{name}}",
+  "Manage pipeline": "Zarządzaj procesem",
   "Manage roles": "Zarządzaj rolami",
   "Manage site": "Zarządzaj lokalizacjami",
   "Manage user groups": "Zarządzaj grupami użytkowników",
@@ -705,7 +705,6 @@
   "Name + Value + Percent": "Nazwa + Wartość + Procent",
   "Name used for the grouped remaining slices.": "Nazwa używana dla 
zgrupowanych pozostałych segmentów.",
   "Navigate": "Przejdź",
-  "Navigate to pipeline overview afterwards": "Następnie przejdź do przeglądu 
strumieni",
   "Nested": "Zagnieżdżone",
   "Network Error": "Błąd sieci",
   "New": "Nowy",
@@ -797,15 +796,9 @@
   "Pie": "Wykres kołowy",
   "Pin": "Pinezka",
   "Pipeline": "strumień",
-  "Pipeline Name": "Nazwa strumienia",
   "Pipeline as code": "Strumień jako kod",
-  "Pipeline description must not have more than 80 characters.": "Opis 
strumieni nie może mieć więcej niż 80 znaków.",
   "Pipeline elements": "Elementy strumienia",
   "Pipeline health monitoring discovered the following issues:": 
"Monitorowanie kondycji strumienia wykryło następujące problemy:",
-  "Pipeline name can only contain letters, numbers, dashes (-), and 
underscores (_).": "Nazwa strumieni może zawierać tylko litery, cyfry, myślniki 
(-) i podkreślenia (_).",
-  "Pipeline name cannot start or end with a space.": "Nazwa strumienia nie 
może zaczynać się ani kończyć spacją.",
-  "Pipeline name is required.": "Nazwa strumienia jest wymagana.",
-  "Pipeline name must have between 3 and 50 characters.": "Nazwa strumieni 
musi mieć od 3 do 50 znaków.",
   "Pipelines": "Strumienie",
   "Places labels inside slices or outside the chart.": "Umieszcza etykiety 
wewnątrz segmentów lub poza wykresem.",
   "Please change the adapter configuration to fix them.": "Zmień konfigurację 
adaptera, aby je naprawić.",
@@ -943,7 +936,7 @@
   "Save pipeline": "Zapisz strumień",
   "Save template": "Zapisz szablon",
   "Saving metadata": "Zapisywanie metadanych",
-  "Saving pipeline": "Zapisywanie strumienia",
+  "Saving pipeline": "Zapisywanie potoku",
   "Saving pipeline modifications": "Zapisywanie zmian w strumieniu",
   "Scatter": "Punktowy",
   "Scattered Line": "Linia punktowa",
@@ -1020,7 +1013,6 @@
   "Show latest time above card": "Pokaż najnowszy czas nad kartą",
   "Show legend": "Pokaż legendę",
   "Show only recommended settings": "Pokaż tylko zalecane ustawienia",
-  "Show pipeline configuration as code": "Pokaż konfigurację strumienia jako 
kod",
   "Show progress label": "Pokaż etykietę postępu",
   "Show raw data from your data source.": "Pokaż surowe dane ze źródła.",
   "Show single data entry.": "Pokaż pojedynczy wpis danych.",
@@ -1063,12 +1055,11 @@
   "Start export process": "Rozpocznij proces eksportu",
   "Start import process": "Rozpocznij proces importu",
   "Start pipeline": "Uruchom strumień",
-  "Start pipeline immediately": "Uruchom strumień od razu",
   "Start selected adapters": "Uruchom wybrane adaptery",
   "Starting": "Uruchamianie",
   "Starting adapter": "Uruchamianie adaptera",
   "Starting adapter {{adapterName}}": "Uruchamianie adaptera {{adapterName}}",
-  "Starting pipeline": "Uruchamianie strumienia",
+  "Starting pipeline": "Uruchamianie potoku",
   "Starting pipeline ": "Uruchamianie strumienia",
   "State": "Stan",
   "Status": "Status",
@@ -1083,6 +1074,7 @@
   "Stopping pipeline": "Zatrzymywanie strumienia",
   "Stopping pipeline...": "Zatrzymywanie strumienia...",
   "Store all events of this source in the internal data store": "Przechowuj 
wszystkie zdarzenia z tego źródła w wewnętrznej bazie danych",
+  "Store and Start": "Zapisz i uruchom",
   "Store as template": "Zapisz jako szablon",
   "Style/Tile server URL": "URL stylu/serwera kafelków",
   "Success": "Sukces",
diff --git 
a/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
 
b/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
index 065c598477..620c74e488 100644
--- 
a/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
+++ 
b/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
@@ -26,7 +26,7 @@
     >
         <div fxLayout="row">
             @if (pipelineOperationStatus()?.success) {
-                <mat-icon data-cy="sp-pipeline-started-success" color="accent"
+                <mat-icon data-cy="sp-pipeline-started" color="accent"
                     >done</mat-icon
                 >
             }
diff --git 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.html
 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.html
index a55604f12d..07d588c2b5 100644
--- 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.html
+++ 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.html
@@ -17,20 +17,16 @@
 -->
 
 <div fxFlex="100" fxLayout="row" fxLayoutAlign="start center">
-    <button
-        mat-flat-button
-        [matTooltip]="'Save pipeline' | translate"
-        [matTooltipPosition]="'above'"
+    <sp-split-button
+        dataCy="sp-editor-save-pipeline"
+        [label]="'Store and Start' | translate"
+        icon="save"
+        [actions]="savePipelineActions"
         [disabled]="!pipelineValidationService.pipelineValid"
-        (click)="savePipelineEmitter.emit()"
-        type="submit"
-        data-cy="sp-editor-save-pipeline"
-    >
-        <div fxLayoutAlign="start center" fxLayout="row">
-            <i class="material-icons">save</i>
-            <span>&nbsp;{{ 'Save' | translate }}</span>
-        </div>
-    </button>
+        [menuAriaLabel]="'Select save action'"
+        (primaryAction)="emitSavePipeline(true)"
+        (actionSelected)="onSavePipelineActionSelected($event)"
+    ></sp-split-button>
     <span class="assembly-options-divider"></span>
     <button
         mat-flat-button
@@ -88,7 +84,36 @@
         #assemblyOptionsPipelineCacheComponent
     >
     </sp-pipeline-assembly-options-pipeline-cache>
+
     <span fxFlex></span>
+    @if (editMode) {
+        <button
+            mat-icon-button
+            [matMenuTriggerFor]="pipelineOptionsMenu"
+            [attr.aria-label]="'Options' | translate"
+            data-cy="options-pipeline"
+        >
+            <mat-icon>more_vert</mat-icon>
+        </button>
+        <mat-menu #pipelineOptionsMenu="matMenu">
+            <button
+                mat-menu-item
+                (click)="managePipelineEmitter.emit()"
+                data-cy="manage-pipeline-btn"
+            >
+                <mat-icon>settings</mat-icon>
+                <span>{{ 'Manage pipeline' | translate }}</span>
+            </button>
+            <button
+                mat-menu-item
+                (click)="deletePipelineEmitter.emit()"
+                data-cy="delete-pipeline-btn"
+            >
+                <mat-icon>clear</mat-icon>
+                <span>{{ 'Delete pipeline' | translate }}</span>
+            </button>
+        </mat-menu>
+    }
     <button
         mat-icon-button
         [matTooltip]="'Clear Assembly Area' | translate"
diff --git 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.ts
 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.ts
index cdeabf855d..44922fabad 100644
--- 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.ts
+++ 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly-options/pipeline-assembly-options.component.ts
@@ -31,6 +31,8 @@ import {
     ConfirmDialogComponent,
     DialogService,
     PanelType,
+    SpSplitButtonAction,
+    SpSplitButtonComponent,
 } from '@streampipes/shared-ui';
 import { EditorService } from '../../../services/editor.service';
 import { MatDialog } from '@angular/material/dialog';
@@ -51,9 +53,15 @@ import {
     LayoutDirective,
 } from '@ngbracket/ngx-layout/flex';
 import { MatButton, MatIconButton } from '@angular/material/button';
+import { MatIcon } from '@angular/material/icon';
+import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
 import { MatTooltip } from '@angular/material/tooltip';
 import { TranslatePipe } from '@ngx-translate/core';
 
+export interface PipelineAssemblySaveOptions {
+    startPipelineAfterStorage: boolean;
+}
+
 @Component({
     selector: 'sp-pipeline-assembly-options',
     templateUrl: './pipeline-assembly-options.component.html',
@@ -63,13 +71,25 @@ import { TranslatePipe } from '@ngx-translate/core';
         LayoutDirective,
         LayoutAlignDirective,
         MatButton,
+        MatIcon,
         MatTooltip,
         MatIconButton,
+        MatMenuTrigger,
+        MatMenu,
+        MatMenuItem,
         PipelineAssemblyOptionsPipelineCacheComponent,
+        SpSplitButtonComponent,
         TranslatePipe,
     ],
 })
 export class PipelineAssemblyOptionsComponent {
+    savePipelineActions: SpSplitButtonAction[] = [
+        {
+            label: 'Store',
+            action: 'store',
+            icon: 'save',
+        },
+    ];
     editorService = inject(EditorService);
     pipelineValidationService = inject(PipelineValidationService);
     private pipelinePositioningService = inject(PipelinePositioningService);
@@ -91,8 +111,12 @@ export class PipelineAssemblyOptionsComponent {
     @Input()
     previewModeActive: boolean;
 
+    @Input()
+    editMode = false;
+
     @Output()
-    savePipelineEmitter: EventEmitter<void> = new EventEmitter<void>();
+    savePipelineEmitter: EventEmitter<PipelineAssemblySaveOptions> =
+        new EventEmitter<PipelineAssemblySaveOptions>();
 
     @Output()
     clearAssemblyEmitter: EventEmitter<void> = new EventEmitter<void>();
@@ -104,6 +128,12 @@ export class PipelineAssemblyOptionsComponent {
     displayPipelineTemplateEmitter: EventEmitter<Pipeline> =
         new EventEmitter<Pipeline>();
 
+    @Output()
+    managePipelineEmitter: EventEmitter<void> = new EventEmitter<void>();
+
+    @Output()
+    deletePipelineEmitter: EventEmitter<void> = new EventEmitter<void>();
+
     @ViewChild('assemblyOptionsPipelineCacheComponent')
     assemblyOptionsCacheComponent: 
PipelineAssemblyOptionsPipelineCacheComponent;
 
@@ -168,6 +198,16 @@ export class PipelineAssemblyOptionsComponent {
         );
     }
 
+    emitSavePipeline(startPipelineAfterStorage: boolean): void {
+        this.savePipelineEmitter.emit({
+            startPipelineAfterStorage,
+        });
+    }
+
+    onSavePipelineActionSelected(action: SpSplitButtonAction): void {
+        this.emitSavePipeline(action.action === 'store-and-start');
+    }
+
     triggerCacheUpdate(): void {
         this.assemblyOptionsCacheComponent.triggerPipelineCacheUpdate();
     }
diff --git 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html
 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html
index 3914c4fc89..5d423ce280 100644
--- 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html
+++ 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html
@@ -26,9 +26,12 @@
             [allElements]="allElements"
             [jsplumbBridge]="jsplumbBridge"
             [previewModeActive]="previewModeActive"
+            [editMode]="!!originalPipeline"
             (clearAssemblyEmitter)="clearAssembly()"
             (togglePreviewEmitter)="togglePreview()"
-            (savePipelineEmitter)="submit()"
+            (savePipelineEmitter)="submit($event)"
+            (managePipelineEmitter)="managePipeline()"
+            (deletePipelineEmitter)="deletePipeline()"
             (displayPipelineTemplateEmitter)="displayPipelineTemplate($event)"
         >
         </sp-pipeline-assembly-options>
diff --git 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
index 780a3a4bce..ffcdbed5fc 100644
--- 
a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
+++ 
b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
@@ -19,6 +19,7 @@
 import {
     AfterViewInit,
     Component,
+    EventEmitter,
     Input,
     OnDestroy,
     ViewChild,
@@ -28,31 +29,46 @@ import { JsplumbBridge } from 
'../../services/jsplumb-bridge.service';
 import { PipelinePositioningService } from 
'../../services/pipeline-positioning.service';
 import { PipelineValidationService } from 
'../../services/pipeline-validation.service';
 import {
+    InvocablePipelineElementUnion,
     PipelineElementConfig,
     PipelineElementUnion,
 } from '../../model/editor.model';
 import { ObjectProvider } from '../../services/object-provider.service';
 import {
+    AssetSaveService,
     DialogService,
     KeyboardShortcutService,
+    ObjectManageDialogComponent,
+    ObjectManageDialogResourceConfig,
+    ObjectManageDialogResult,
     PanelType,
     ShortcutRegistration,
     SpBasicViewComponent,
 } from '@streampipes/shared-ui';
-import { SavePipelineComponent } from 
'../../dialog/save-pipeline/save-pipeline.component';
 import { EditorService } from '../../services/editor.service';
 import {
+    LinkageData,
     Pipeline,
     PipelineCanvasMetadata,
+    PermissionsService,
 } from '@streampipes/platform-services';
 import { JsplumbFactoryService } from '../../services/jsplumb-factory.service';
-import { forkJoin } from 'rxjs';
+import { firstValueFrom, forkJoin } from 'rxjs';
 import { Router } from '@angular/router';
 import { PipelineAssemblyDrawingAreaComponent } from 
'./pipeline-assembly-drawing-area/pipeline-assembly-drawing-area.component';
-import { PipelineAssemblyOptionsComponent } from 
'./pipeline-assembly-options/pipeline-assembly-options.component';
+import {
+    PipelineAssemblyOptionsComponent,
+    PipelineAssemblySaveOptions,
+} from './pipeline-assembly-options/pipeline-assembly-options.component';
 import { JsplumbService } from '../../services/jsplumb.service';
 import { TranslateService } from '@ngx-translate/core';
 import { FlexDirective } from '@ngbracket/ngx-layout/flex';
+import { PipelineOperationsService } from 
'../../../pipelines/services/pipeline-operations.service';
+import { IdGeneratorService } from 
'../../../core-services/id-generator/id-generator.service';
+import {
+    SavePipelineComponent,
+    SavePipelineDialogResult,
+} from '../../dialog/save-pipeline/save-pipeline.component';
 
 @Component({
     selector: 'sp-pipeline-assembly',
@@ -76,6 +92,10 @@ export class PipelineAssemblyComponent implements 
AfterViewInit, OnDestroy {
     private jsplumbService = inject(JsplumbService);
     private translateService = inject(TranslateService);
     private shortcutService = inject(KeyboardShortcutService);
+    private permissionsService = inject(PermissionsService);
+    private assetSaveService = inject(AssetSaveService);
+    private pipelineOperationsService = inject(PipelineOperationsService);
+    private idGeneratorService = inject(IdGeneratorService);
 
     @Input()
     rawPipelineModel: PipelineElementConfig[];
@@ -83,6 +103,9 @@ export class PipelineAssemblyComponent implements 
AfterViewInit, OnDestroy {
     @Input()
     originalPipeline: Pipeline;
 
+    @Input()
+    cloneMode = false;
+
     @Input()
     pipelineCanvasMetadata: PipelineCanvasMetadata;
 
@@ -94,6 +117,7 @@ export class PipelineAssemblyComponent implements 
AfterViewInit, OnDestroy {
 
     previewModeActive = false;
     readonly: boolean;
+    private pendingManagePipelineResult?: ObjectManageDialogResult<Pipeline>;
 
     jsplumbBridge: JsplumbBridge;
     private shortcutReg: ShortcutRegistration;
@@ -146,7 +170,39 @@ export class PipelineAssemblyComponent implements 
AfterViewInit, OnDestroy {
     /**
      * Sends the pipeline to the server
      */
-    submit() {
+    submit(
+        saveOptions: PipelineAssemblySaveOptions = {
+            startPipelineAfterStorage: true,
+        },
+    ) {
+        const pipeline = this.makePipelineForSave();
+
+        if (this.originalPipeline && this.cloneMode) {
+            this.prepareClonedPipeline(pipeline);
+            this.openCreatePipelineDialog(
+                pipeline,
+                saveOptions.startPipelineAfterStorage,
+                this.makeClonedPipelineCanvasMetadata(),
+            );
+            return;
+        }
+
+        if (this.originalPipeline) {
+            void this.savePipelineChanges(
+                pipeline,
+                saveOptions.startPipelineAfterStorage,
+            );
+            return;
+        }
+
+        this.openCreatePipelineDialog(
+            pipeline,
+            saveOptions.startPipelineAfterStorage,
+            this.pipelineCanvasMetadata,
+        );
+    }
+
+    private makePipelineForSave(): Pipeline {
         const pipelineModel = this.rawPipelineModel;
         const pipeline = this.objectProvider.makePipeline(pipelineModel);
         this.pipelinePositioningService.collectPipelineElementPositions(
@@ -157,27 +213,243 @@ export class PipelineAssemblyComponent implements 
AfterViewInit, OnDestroy {
             pipelineModel,
             this.readonly,
         );
-        const dialogRef = this.dialogService.open(SavePipelineComponent, {
+        return pipeline;
+    }
+
+    private openCreatePipelineDialog(
+        pipeline: Pipeline,
+        startPipelineAfterStorage: boolean,
+        pipelineCanvasMetadata: PipelineCanvasMetadata,
+    ): void {
+        const resourceConfig: ObjectManageDialogResourceConfig<Pipeline> = {
+            resourceLabel: 'Pipeline',
+            nameLabel: 'Pipeline name',
+            descriptionLabel: 'Description',
+            nameProperty: 'name',
+            assetLinkType: 'pipeline',
+            assetLinkCheckboxLabel:
+                'Add the current pipeline to an existing asset',
+            saveResource: async resource => {
+                const saveSuccessful = await this.savePipelineResource(
+                    resource,
+                    startPipelineAfterStorage,
+                    false,
+                    pipelineCanvasMetadata,
+                );
+                if (!saveSuccessful) {
+                    throw new Error('Saving the pipeline failed.');
+                }
+            },
+        };
+        const dialogRef = this.dialogService.open(ObjectManageDialogComponent, 
{
             panelType: PanelType.SLIDE_IN_PANEL,
             title: this.translateService.instant('Save pipeline'),
-            width: '40vw',
+            width: '50vw',
             data: {
-                pipeline: pipeline,
-                originalPipeline: this.originalPipeline,
-                pipelineCanvasMetadata: this.pipelineCanvasMetadata,
+                createMode: true,
+                resource: JSON.parse(JSON.stringify(pipeline)),
+                saveMode: 'immediate',
+                resourceConfig,
+                headerTitle: this.translateService.instant('Save pipeline'),
             },
         });
-        dialogRef
-            .afterClosed()
-            .subscribe((config: { reload: boolean; pipelineId: string }) => {
-                if (config?.reload) {
-                    this.clearAssembly();
-                    this.rawPipelineModel = [];
-                    setTimeout(() => {
-                        this.router.navigate(['pipelines', 'create']);
-                    });
-                }
-            });
+        dialogRef.afterClosed().subscribe(refresh => {
+            if (refresh) {
+                this.editorService.makePipelineAssemblyEmpty(true);
+                this.editorService.removePipelineFromCache().subscribe();
+                this.router.navigate(['pipelines']);
+            }
+        });
+    }
+
+    managePipeline(): void {
+        if (!this.originalPipeline) {
+            return;
+        }
+
+        const resource: Pipeline = { ...this.originalPipeline };
+        const resourceConfig: ObjectManageDialogResourceConfig<Pipeline> = {
+            resourceLabel: 'Pipeline',
+            nameLabel: 'Pipeline name',
+            descriptionLabel: 'Description',
+            nameProperty: 'name',
+            assetLinkType: 'pipeline',
+            assetLinkCheckboxLabel:
+                'Add the current pipeline to an existing asset',
+        };
+
+        const dialogRef = this.dialogService.open(ObjectManageDialogComponent, 
{
+            panelType: PanelType.SLIDE_IN_PANEL,
+            title: this.translateService.instant('Manage'),
+            width: '50vw',
+            data: {
+                objectInstanceId: resource._id,
+                resource,
+                saveMode: 'deferred',
+                resourceConfig,
+                headerTitle:
+                    this.translateService.instant('Manage Pipeline ') +
+                    resource.name,
+            },
+        });
+
+        dialogRef.afterClosed().subscribe(result => {
+            if (result && typeof result !== 'boolean') {
+                this.pendingManagePipelineResult = result;
+                Object.assign(this.originalPipeline, result.resource);
+            }
+        });
+    }
+
+    deletePipeline(): void {
+        if (!this.originalPipeline) {
+            return;
+        }
+
+        this.pipelineOperationsService.showDeleteDialog(
+            this.originalPipeline._id,
+            this.originalPipeline.name,
+            this.originalPipeline.running,
+            new EventEmitter<boolean>(),
+            () => this.router.navigate(['pipelines']),
+        );
+    }
+
+    private async savePipelineChanges(
+        pipeline: Pipeline,
+        startPipelineAfterStorage: boolean,
+    ): Promise<void> {
+        pipeline._id = this.originalPipeline._id;
+        pipeline.name = this.originalPipeline.name;
+        pipeline.description = this.originalPipeline.description;
+        pipeline.running = this.originalPipeline.running;
+        pipeline.createdAt = this.originalPipeline.createdAt;
+        pipeline.createdByUser = this.originalPipeline.createdByUser;
+
+        const saveSuccessful = await this.savePipelineResource(
+            pipeline,
+            startPipelineAfterStorage,
+            true,
+            this.pipelineCanvasMetadata,
+        );
+        if (!saveSuccessful) {
+            return;
+        }
+
+        await this.savePendingManagePipelineChanges();
+        this.editorService.makePipelineAssemblyEmpty(true);
+        this.editorService.removePipelineFromCache().subscribe();
+        this.router.navigate(['pipelines']);
+    }
+
+    private async savePipelineResource(
+        pipeline: Pipeline,
+        startPipelineAfterStorage: boolean,
+        updateExisting: boolean,
+        pipelineCanvasMetadata: PipelineCanvasMetadata,
+    ): Promise<boolean> {
+        const dialogRef = this.dialogService.open(SavePipelineComponent, {
+            panelType: PanelType.STANDARD_PANEL,
+            title: this.translateService.instant('Save pipeline'),
+            width: '70vw',
+            disableClose: true,
+            data: {
+                pipeline,
+                originalPipeline: updateExisting
+                    ? this.originalPipeline
+                    : undefined,
+                pipelineCanvasMetadata,
+                startPipelineAfterStorage,
+                updateExisting,
+            },
+        });
+
+        const result = (await firstValueFrom(
+            dialogRef.afterClosed(),
+        )) as SavePipelineDialogResult;
+        if (result?.pipelineId) {
+            pipeline._id = result.pipelineId;
+        }
+        return !!result?.success;
+    }
+
+    private async savePendingManagePipelineChanges(): Promise<void> {
+        const result = this.pendingManagePipelineResult;
+        if (!result) {
+            return;
+        }
+
+        if (result.permission) {
+            await firstValueFrom(
+                this.permissionsService.updatePermission(result.permission),
+            );
+        }
+
+        if (this.shouldSaveManagePipelineAssets(result)) {
+            await this.assetSaveService.saveSelectedAssets(
+                result.selectedAssets,
+                this.createPipelineLinkageData(result.resource),
+                result.deselectedAssets,
+                result.originalAssets,
+            );
+        }
+
+        this.pendingManagePipelineResult = undefined;
+    }
+
+    private shouldSaveManagePipelineAssets(
+        result: ObjectManageDialogResult<Pipeline>,
+    ): boolean {
+        return (
+            result.addToAssets &&
+            (result.selectedAssets.length > 0 ||
+                result.deselectedAssets.length > 0 ||
+                result.originalAssets.length > 0)
+        );
+    }
+
+    private createPipelineLinkageData(pipeline: Pipeline): LinkageData[] {
+        return [
+            {
+                type: 'pipeline',
+                id: pipeline._id ?? '',
+                name: pipeline.name ?? '',
+            },
+        ];
+    }
+
+    private prepareClonedPipeline(pipeline: Pipeline): void {
+        pipeline._id = undefined;
+        pipeline._rev = undefined;
+        pipeline.name = `${this.originalPipeline.name}_cloned`;
+        pipeline.description = this.originalPipeline.description;
+        pipeline.running = false;
+        pipeline.actions.forEach(element =>
+            this.updateInvocablePipelineElementId(element),
+        );
+        pipeline.sepas.forEach(element =>
+            this.updateInvocablePipelineElementId(element),
+        );
+    }
+
+    private updateInvocablePipelineElementId(
+        entity: InvocablePipelineElementUnion,
+    ): void {
+        const lastIdIndex = entity.elementId.lastIndexOf(':');
+        entity.elementId =
+            entity.elementId.substring(0, lastIdIndex + 1) +
+            this.idGeneratorService.generate(5);
+    }
+
+    private makeClonedPipelineCanvasMetadata(): PipelineCanvasMetadata {
+        const metadata = PipelineCanvasMetadata.fromData(
+            this.pipelineCanvasMetadata,
+            new PipelineCanvasMetadata(),
+        );
+        metadata._id = undefined;
+        metadata._rev = undefined;
+        metadata.pipelineId = undefined;
+        return metadata;
     }
 
     togglePreview(): void {
diff --git 
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.html
 
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.html
deleted file mode 100644
index 2fa9e2902a..0000000000
--- 
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.html
+++ /dev/null
@@ -1,177 +0,0 @@
-<!--
-~ 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 fxLayout="column">
-    <form [formGroup]="submitPipelineForm">
-        <div fxFlex="100" fxLayout="column">
-            <sp-form-field [level]="3" [label]="'Pipeline Name' | translate">
-                <mat-form-field fxFlex color="accent">
-                    <input
-                        [formControlName]="'pipelineName'"
-                        data-cy="sp-editor-pipeline-name"
-                        matInput
-                        name="pipelineName"
-                        (blur)="triggerTutorial()"
-                    />
-                    @if (
-                        submitPipelineForm
-                            .get('pipelineName')
-                            .hasError('required') ||
-                        submitPipelineForm
-                            .get('pipelineName')
-                            .hasError('whiteSpaceOnly')
-                    ) {
-                        <mat-error>
-                            {{ 'Pipeline name is required.' | translate }}
-                        </mat-error>
-                    }
-                    @if (
-                        submitPipelineForm
-                            .get('pipelineName')
-                            .hasError('leadingOrTrailingWhitespace')
-                    ) {
-                        <mat-error>
-                            {{
-                                'Pipeline name cannot start or end with a 
space.'
-                                    | translate
-                            }}
-                        </mat-error>
-                    }
-                    @if (
-                        submitPipelineForm
-                            .get('pipelineName')
-                            .hasError('minlength') ||
-                        submitPipelineForm
-                            .get('pipelineName')
-                            .hasError('maxlength')
-                    ) {
-                        <mat-error>
-                            {{
-                                'Pipeline name must have between 3 and 50 
characters.'
-                                    | translate
-                            }}
-                        </mat-error>
-                    }
-                    @if (
-                        submitPipelineForm
-                            .get('pipelineName')
-                            .hasError('invalidName')
-                    ) {
-                        <mat-error>
-                            {{
-                                'Pipeline name can only contain letters, 
numbers, dashes (-), and underscores (_).'
-                                    | translate
-                            }}
-                        </mat-error>
-                    }
-                </mat-form-field>
-            </sp-form-field>
-            <sp-form-field [level]="3" [label]="'Description' | translate">
-                <mat-form-field fxFlex color="accent">
-                    <input [formControlName]="'pipelineDescription'" matInput 
/>
-                    <mat-error
-                        >{{
-                            'Pipeline description must not have more than 80 
characters.'
-                                | translate
-                        }}
-                    </mat-error>
-                </mat-form-field>
-            </sp-form-field>
-        </div>
-    </form>
-    @if (storageOptions.updateModeActive) {
-        <div id="overwriteCheckbox" class="checkbox">
-            <mat-radio-group
-                [(ngModel)]="storageOptions.updateMode"
-                fxLayout="column"
-                color="accent"
-                class="pipeline-radio-group"
-            >
-                <mat-radio-button
-                    [value]="'update'"
-                    style="padding-left: 0"
-                    data-cy="pipeline-update-mode-update"
-                >
-                    {{ 'Update pipeline' | translate }}
-                </mat-radio-button>
-                <mat-radio-button
-                    [value]="'clone'"
-                    class="mb-10"
-                    data-cy="pipeline-update-mode-clone"
-                >
-                    {{ 'Create new pipeline' | translate }}
-                </mat-radio-button>
-            </mat-radio-group>
-        </div>
-    }
-    <sp-split-section [level]="3" [title]="'Options' | translate">
-        <mat-checkbox
-            [(ngModel)]="storageOptions.startPipelineAfterStorage"
-            color="accent"
-            data-cy="sp-editor-checkbox-start-immediately"
-        >
-            {{ 'Start pipeline immediately' | translate }}
-        </mat-checkbox>
-        <mat-checkbox
-            [(ngModel)]="storageOptions.navigateToPipelineOverview"
-            color="accent"
-            data-cy="sp-editor-checkbox-navigate-to-overview"
-        >
-            {{ 'Navigate to pipeline overview afterwards' | translate }}
-        </mat-checkbox>
-        @if (isAssetAdmin) {
-            <mat-checkbox
-                [(ngModel)]="addToAssets"
-                color="accent"
-                data-cy="sp-show-pipeline-asset-checkbox"
-            >
-                {{ 'Add Pipeline to Assets' | translate }}
-            </mat-checkbox>
-            @if (addToAssets) {
-                <div class="mt-10">
-                    <sp-asset-link-configuration
-                        [isEdit]="storageOptions.updateMode === 'update'"
-                        [itemId]="pipeline._id"
-                        (selectedAssetsChange)="onSelectedAssetsChange($event)"
-                        (deselectedAssetsChange)="
-                            onDeselectedAssetsChange($event)
-                        "
-                        (originalAssetsEmitter)="
-                            onOriginalAssetsEmitted($event)
-                        "
-                    >
-                    </sp-asset-link-configuration>
-                </div>
-            }
-        }
-    </sp-split-section>
-    <div class="mt-10">
-        <mat-expansion-panel class="mat-elevation-z0 border-1">
-            <mat-expansion-panel-header>{{
-                'Show pipeline configuration as code' | translate
-            }}</mat-expansion-panel-header>
-            @if (compactPipeline) {
-                <sp-configuration-code-panel
-                    [configuration]="compactPipeline"
-                    maxHeight="none"
-                >
-                </sp-configuration-code-panel>
-            }
-        </mat-expansion-panel>
-    </div>
-</div>
diff --git 
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.scss
 
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.scss
deleted file mode 100644
index e22ed79985..0000000000
--- 
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.
- *
- */
-
-.border-1 {
-    border: 1px solid var(--color-bg-2);
-}
diff --git 
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.ts
 
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.ts
deleted file mode 100644
index 2b82cb626c..0000000000
--- 
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline-settings/save-pipeline-settings.component.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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,
-    EventEmitter,
-    inject,
-    Input,
-    OnInit,
-    Output,
-} from '@angular/core';
-import { ShepherdService } from '../../../../services/tour/shepherd.service';
-import {
-    FormsModule,
-    ReactiveFormsModule,
-    UntypedFormControl,
-    UntypedFormGroup,
-    Validators,
-} from '@angular/forms';
-import {
-    CompactPipeline,
-    Pipeline,
-    PipelineService,
-    SpAssetTreeNode,
-    UserInfo,
-} from '@streampipes/platform-services';
-import { PipelineStorageOptions } from '../../../model/editor.model';
-import { ValidateName } from 
'../../../../core-ui/static-properties/input.validator';
-import {
-    AssetLinkConfigurationComponent,
-    CurrentUserService,
-    FormFieldComponent,
-    SplitSectionComponent,
-} from '@streampipes/shared-ui';
-import { UserRole } from '../../../../core/auth/user-role.enum';
-import { FlexDirective, LayoutDirective } from '@ngbracket/ngx-layout/flex';
-import { MatError, MatFormField } from '@angular/material/form-field';
-import { MatInput } from '@angular/material/input';
-import { MatRadioButton, MatRadioGroup } from '@angular/material/radio';
-import { MatCheckbox } from '@angular/material/checkbox';
-import {
-    MatExpansionPanel,
-    MatExpansionPanelHeader,
-} from '@angular/material/expansion';
-import { ConfigurationCodePanelComponent } from 
'../../../../core-ui/configuration-code-panel/configuration-code-panel.component';
-import { TranslatePipe } from '@ngx-translate/core';
-
-@Component({
-    selector: 'sp-save-pipeline-settings',
-    templateUrl: './save-pipeline-settings.component.html',
-    styleUrls: ['./save-pipeline-settings.component.scss'],
-    imports: [
-        LayoutDirective,
-        FormsModule,
-        ReactiveFormsModule,
-        FlexDirective,
-        FormFieldComponent,
-        MatFormField,
-        MatInput,
-        MatError,
-        MatRadioGroup,
-        MatRadioButton,
-        SplitSectionComponent,
-        MatCheckbox,
-        AssetLinkConfigurationComponent,
-        MatExpansionPanel,
-        MatExpansionPanelHeader,
-        ConfigurationCodePanelComponent,
-        TranslatePipe,
-    ],
-})
-export class SavePipelineSettingsComponent implements OnInit {
-    private readonly currentUserService = inject(CurrentUserService);
-
-    @Input()
-    submitPipelineForm: UntypedFormGroup = new UntypedFormGroup({});
-
-    @Input()
-    pipeline: Pipeline;
-
-    @Input()
-    storageOptions: PipelineStorageOptions;
-
-    @Input()
-    currentPipelineName: string;
-
-    private shepherdService = inject(ShepherdService);
-    private pipelineService = inject(PipelineService);
-
-    compactPipeline: CompactPipeline;
-    currentUser: UserInfo;
-    isAssetAdmin = false;
-
-    addToAssets: boolean = false;
-    @Input()
-    selectedAssets: SpAssetTreeNode[];
-    @Input()
-    deselectedAssets: SpAssetTreeNode[];
-    @Input()
-    originalAssets: SpAssetTreeNode[];
-
-    @Output() selectedAssetsChange = new EventEmitter<SpAssetTreeNode[]>();
-    @Output() deselectedAssetsChange = new EventEmitter<SpAssetTreeNode[]>();
-    @Output() originalAssetsChange = new EventEmitter<SpAssetTreeNode[]>();
-
-    ngOnInit() {
-        this.currentUser = this.currentUserService.getCurrentUser();
-        this.isAssetAdmin = this.currentUserService.hasRole(
-            UserRole.ROLE_ASSET_ADMIN,
-        );
-        this.submitPipelineForm.addControl(
-            'pipelineName',
-            new UntypedFormControl(this.pipeline.name, [
-                Validators.required,
-                Validators.minLength(3),
-                Validators.maxLength(50),
-                ValidateName(),
-            ]),
-        );
-        this.submitPipelineForm.addControl(
-            'pipelineDescription',
-            new UntypedFormControl(this.pipeline.description, [
-                Validators.maxLength(80),
-            ]),
-        );
-
-        
this.submitPipelineForm.controls['pipelineName'].valueChanges.subscribe(
-            value => {
-                this.pipeline.name = value;
-            },
-        );
-
-        this.submitPipelineForm.controls[
-            'pipelineDescription'
-        ].valueChanges.subscribe(value => {
-            this.pipeline.description = value;
-        });
-        this.pipelineService
-            .convertToCompactPipeline(this.pipeline)
-            .subscribe(p => (this.compactPipeline = p));
-        if (this.storageOptions.updateModeActive) {
-            this.addToAssets = true;
-        }
-    }
-
-    onSelectedAssetsChange(updatedAssets: SpAssetTreeNode[]): void {
-        this.selectedAssets = updatedAssets;
-        this.selectedAssetsChange.emit(this.selectedAssets);
-    }
-
-    onDeselectedAssetsChange(updatedAssets: SpAssetTreeNode[]): void {
-        this.deselectedAssets = updatedAssets;
-        this.deselectedAssetsChange.emit(this.deselectedAssets);
-    }
-
-    onOriginalAssetsEmitted(updatedAssets: SpAssetTreeNode[]): void {
-        this.originalAssets = updatedAssets;
-        this.originalAssetsChange.emit(this.originalAssets);
-    }
-
-    triggerTutorial() {
-        if (this.shepherdService.isTourActive()) {
-            this.shepherdService.trigger('save-pipeline-dialog');
-        }
-    }
-}
diff --git 
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html 
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
index 51004cc737..dfd13185d1 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
+++ b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
@@ -17,98 +17,64 @@
 -->
 
 <div class="sp-dialog-container">
-    <div class="sp-dialog-content padding-20">
-        <div fxFlex="100" fxLayout="column">
-            @if (
-                !operationCompleted &&
-                !operationProgress &&
-                !pipelineUpdatePreflight
-            ) {
-                <sp-save-pipeline-settings
-                    [currentPipelineName]="pipeline.name"
-                    [submitPipelineForm]="submitPipelineForm"
-                    [pipeline]="pipeline"
-                    [storageOptions]="storageOptions"
-                    [(selectedAssets)]="selectedAssets"
-                    [(deselectedAssets)]="deselectedAssets"
-                    [(originalAssets)]="originalAssets"
-                >
-                </sp-save-pipeline-settings>
-            }
-
-            @if (
-                pipelineUpdatePreflight &&
-                !operationCompleted &&
-                !operationProgress
-            ) {
-                <sp-save-pipeline-update-migration
-                    [measurementUpdateInfos]="measurementUpdateInfos"
-                    (startUpdateEmitter)="savePipeline(true)"
-                >
-                </sp-save-pipeline-update-migration>
-            }
-
-            @if (operationProgress || operationCompleted) {
-                <sp-multi-step-status-indicator
-                    [statusIndicators]="statusIndicators"
-                >
-                </sp-multi-step-status-indicator>
-            }
+    <div class="sp-dialog-content p-15">
+        <div fxLayout="column" fxLayoutAlign="center center" fxFlex="100">
+            <div fxFlex="100" fxLayout="column" class="w-100">
+                @if (
+                    pipelineUpdatePreflight &&
+                    !operationCompleted &&
+                    !operationProgress
+                ) {
+                    <sp-save-pipeline-update-migration
+                        fxFlex="100"
+                        [measurementUpdateInfos]="measurementUpdateInfos"
+                        (startUpdateEmitter)="savePipeline(true)"
+                    >
+                    </sp-save-pipeline-update-migration>
+                }
 
-            @if (finalPipelineOperationStatus) {
-                <div class="mt-10">
-                    <mat-divider></mat-divider>
-                    <sp-pipeline-started-status
-                        class="mt-10"
-                        [forceStopDisabled]="true"
-                        [action]="pipelineAction"
-                        
[pipelineOperationStatus]="finalPipelineOperationStatus"
+                @if (operationProgress || operationCompleted) {
+                    <sp-multi-step-status-indicator
+                        [statusIndicators]="statusIndicators"
                     >
-                    </sp-pipeline-started-status>
-                </div>
-            }
+                    </sp-multi-step-status-indicator>
+                }
+
+                @if (finalPipelineOperationStatus) {
+                    <div class="mt-10">
+                        <mat-divider></mat-divider>
+                        <sp-pipeline-started-status
+                            class="mt-10"
+                            [forceStopDisabled]="true"
+                            [action]="pipelineAction"
+                            [pipelineOperationStatus]="
+                                finalPipelineOperationStatus
+                            "
+                        >
+                        </sp-pipeline-started-status>
+                    </div>
+                }
+            </div>
         </div>
     </div>
     <mat-divider></mat-divider>
-    <div class="sp-dialog-actions" fxLayoutGap="10px">
-        @if (operationCompleted) {
-            <button mat-flat-button (click)="hide(false)">
-                Create another pipeline
-            </button>
-        }
+    <div class="sp-dialog-actions actions-align-right" fxLayoutGap="10px">
         @if (operationCompleted) {
             <button
                 mat-flat-button
                 class="mat-basic"
-                data-cy="sp-navigate-to-pipeline-overview"
-                (click)="navigateToPipelineOverview()"
-            >
-                Open pipeline overview
-            </button>
-        }
-        @if (
-            !operationCompleted && !operationSuccess && 
!pipelineUpdatePreflight
-        ) {
-            <button
-                [disabled]="
-                    !submitPipelineForm.valid ||
-                    operationProgress ||
-                    operationCompleted
-                "
-                mat-flat-button
-                color="accent"
-                (click)="savePipeline()"
-                data-cy="sp-editor-apply"
+                data-cy="sp-save-pipeline-status-close"
+                (click)="close()"
             >
-                {{ 'Apply' | translate }}
+                {{ 'Close' | translate }}
             </button>
         }
-        @if (!operationProgress && (!operationCompleted || !operationSuccess)) 
{
+        @if (pipelineUpdatePreflight && !operationProgress) {
             <button
                 mat-flat-button
                 class="mat-basic"
-                (click)="hide(true)"
                 data-cy="sp-editor-cancel"
+                (click)="close()"
             >
                 {{ 'Cancel' | translate }}
             </button>
diff --git 
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss 
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
index cdfe259c98..13cbc4aacb 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
+++ b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.scss
@@ -15,21 +15,3 @@
  * limitations under the License.
  *
  */
-
-.customize-section {
-    display: flex;
-    flex: 1 1 auto;
-    padding: 20px;
-}
-
-.padding-20 {
-    padding: 20px;
-}
-
-.mb-10 {
-    margin-bottom: 10px;
-}
-
-::ng-deep .pipeline-radio-group .mat-radio-label {
-    padding: 0;
-}
diff --git a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts 
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
index 1cb1fa125c..a6343f925e 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
+++ b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
@@ -16,32 +16,18 @@
  *
  */
 
-import { Component, inject, Input, OnInit } from '@angular/core';
-import { AssetSaveService, DialogRef } from '@streampipes/shared-ui';
+import { Component, Input, OnInit, inject } from '@angular/core';
+import { DialogRef } from '@streampipes/shared-ui';
 import {
-    DatalakeRestService,
-    DataSinkInvocation,
-    LinkageData,
+    MeasurementUpdateInfo,
     Message,
     Pipeline,
     PipelineCanvasMetadata,
     PipelineCanvasMetadataService,
     PipelineOperationStatus,
-    MeasurementUpdateInfo,
     PipelineService,
-    SpAssetTreeNode,
 } from '@streampipes/platform-services';
-import { EditorService } from '../../services/editor.service';
-import { ShepherdService } from '../../../services/tour/shepherd.service';
-import { UntypedFormGroup } from '@angular/forms';
-import { Router } from '@angular/router';
-import {
-    InvocablePipelineElementUnion,
-    PipelineStorageOptions,
-} from '../../model/editor.model';
-import { IdGeneratorService } from 
'../../../core-services/id-generator/id-generator.service';
-import { firstValueFrom, lastValueFrom, Observable, of, tap } from 'rxjs';
-import { filter, switchMap } from 'rxjs/operators';
+import { firstValueFrom } from 'rxjs';
 import {
     Status,
     StatusIndicator,
@@ -53,13 +39,17 @@ import {
     LayoutDirective,
     LayoutGapDirective,
 } from '@ngbracket/ngx-layout/flex';
-import { SavePipelineSettingsComponent } from 
'./save-pipeline-settings/save-pipeline-settings.component';
 import { MultiStepStatusIndicatorComponent } from 
'../../../core-ui/multi-step-status-indicator/multi-step-status-indicator.component';
 import { MatDivider } from '@angular/material/divider';
 import { PipelineStartedStatusComponent } from 
'../../../core-ui/pipeline/pipeline-started-status/pipeline-started-status.component';
 import { MatButton } from '@angular/material/button';
 import { SavePipelineUpdateMigrationComponent } from 
'./save-pipeline-update-migration/save-pipeline-update-migration.component';
 
+export interface SavePipelineDialogResult {
+    success: boolean;
+    pipelineId?: string;
+}
+
 @Component({
     selector: 'sp-save-pipeline',
     templateUrl: './save-pipeline.component.html',
@@ -67,178 +57,98 @@ import { SavePipelineUpdateMigrationComponent } from 
'./save-pipeline-update-mig
     imports: [
         FlexDirective,
         LayoutDirective,
-        SavePipelineSettingsComponent,
+        LayoutGapDirective,
         MultiStepStatusIndicatorComponent,
         MatDivider,
         PipelineStartedStatusComponent,
-        LayoutGapDirective,
         MatButton,
         TranslatePipe,
         SavePipelineUpdateMigrationComponent,
     ],
 })
 export class SavePipelineComponent implements OnInit {
-    private editorService = inject(EditorService);
     private dialogRef = inject(DialogRef<SavePipelineComponent>);
-    private idGeneratorService = inject(IdGeneratorService);
     private pipelineService = inject(PipelineService);
-    private router = inject(Router);
-    private shepherdService = inject(ShepherdService);
     private pipelineCanvasService = inject(PipelineCanvasMetadataService);
-    private assetSaveService = inject(AssetSaveService);
-    private dataLakeService = inject(DatalakeRestService);
     private translateService = inject(TranslateService);
 
     @Input()
     pipeline: Pipeline;
 
     @Input()
-    originalPipeline: Pipeline;
-
-    selectedAssets: SpAssetTreeNode[];
-    deselectedAssets: SpAssetTreeNode[];
-    originalAssets: SpAssetTreeNode[];
+    originalPipeline?: Pipeline;
 
     @Input()
     pipelineCanvasMetadata: PipelineCanvasMetadata;
 
+    @Input()
+    startPipelineAfterStorage = true;
+
+    @Input()
+    updateExisting = false;
+
     operationProgress = false;
     operationCompleted = false;
     operationSuccess = false;
 
-    errorMessage = '';
     pipelineId: string;
-
-    storageOptions: PipelineStorageOptions = {
-        updateMode: 'update',
-        startPipelineAfterStorage: true,
-        navigateToPipelineOverview: true,
-        updateModeActive: false,
-    };
-
-    submitPipelineForm: UntypedFormGroup = new UntypedFormGroup({});
     statusIndicators: StatusIndicator[] = [];
-    finalPipelineOperationStatus: PipelineOperationStatus;
-    pipelineAction: PipelineAction;
+    finalPipelineOperationStatus?: PipelineOperationStatus;
+    pipelineAction?: PipelineAction;
     pipelineUpdatePreflight = false;
     measurementUpdateInfos: MeasurementUpdateInfo[] = [];
 
-    ngOnInit() {
-        this.storageOptions.updateModeActive =
-            this.originalPipeline !== undefined;
-        if (this.storageOptions.updateModeActive) {
-            this.pipeline._id = this.originalPipeline._id;
-            this.pipeline.name = this.originalPipeline.name;
-            this.pipeline.description = this.originalPipeline.description;
-            this.pipeline.running = this.originalPipeline.running;
-            this.pipeline.createdAt = this.originalPipeline.createdAt;
-            this.pipeline.createdByUser = this.originalPipeline.createdByUser;
-        }
-
-        if (this.shepherdService.isTourActive()) {
-            this.shepherdService.trigger('enter-pipeline-name');
-        }
+    ngOnInit(): void {
+        void this.savePipeline();
     }
 
-    performStorageOperations(
-        stopPipeline$: Observable<null | PipelineOperationStatus>,
-        savePipeline$: Observable<Message>,
-    ) {
-        // if pipeline is running and update mode: stop pipeline
-        // if update mode: update pipeline, if not update mode or update mode 
clone: save pipeline
-        // if update mode and not clone: update canvas, else store new canvas
-        // if should start: start pipeline
-        stopPipeline$
-            .pipe(
-                tap(() =>
-                    this.addStatusIndicator(
-                        this.translateService.instant('Saving pipeline'),
-                        Status.PROGRESS,
-                    ),
-                ),
-                switchMap(() => savePipeline$),
-                tap(message => {
-                    this.operationSuccess = message.success;
-                    if (!message.success) {
-                        this.handleStorageError();
-                    }
-                    this.modifyStatusIndicator(Status.SUCCESS);
-                    this.pipelineId = message.notifications[1].description;
-                }),
-                // only continue if pipeline was saved
-                filter(message => message.success),
-                tap(() =>
-                    this.addStatusIndicator(
-                        this.translateService.instant('Saving metadata'),
-                        Status.PROGRESS,
-                    ),
-                ),
-                switchMap(() =>
-                    this.getPipelineCanvasMetadata$(this.pipelineId),
-                ),
-                tap(() => this.modifyStatusIndicator(Status.SUCCESS)),
-                switchMap(() => this.getStartPipeline$()),
-            )
-            .subscribe({
-                next: message => {
-                    this.onSuccess(message);
-                    // Add Asset as soon as pipelineId is known
-                    this.addToAsset();
-                },
-                error: msg => {
-                    this.onFailure(msg);
-                },
-            });
-    }
-
-    clonePipeline(): void {
-        this.pipeline._id = undefined;
-        this.pipeline._rev = undefined;
-        this.pipeline.running = false;
-        this.pipeline.actions.forEach(element => this.updateId(element));
-        this.pipeline.sepas.forEach(element => this.updateId(element));
-        this.pipelineCanvasMetadata._id = undefined;
-        this.pipelineCanvasMetadata._rev = undefined;
-    }
-
-    savePipeline(skipPreflight = false) {
-        if (this.shouldPerformUpdatePreflight(skipPreflight)) {
-            this.performUpdatePreflight();
+    async savePipeline(skipPreflight = false): Promise<void> {
+        if (await this.shouldPerformUpdatePreflight(skipPreflight)) {
+            await this.performUpdatePreflight();
             return;
         }
 
         this.pipelineUpdatePreflight = false;
-        let stopPipeline$: Observable<null | PipelineOperationStatus> =
-            of(null);
-        let savePipeline$: Observable<Message> =
-            this.pipelineService.storePipeline(this.pipeline);
         this.operationProgress = true;
-        if (this.storageOptions.updateModeActive) {
-            if (this.storageOptions.updateMode === 'clone') {
-                this.clonePipeline();
-            } else {
-                if (this.pipeline.running) {
-                    stopPipeline$ = this.getStopPipeline$();
+
+        try {
+            if (this.updateExisting && this.pipeline.running) {
+                const stopResult = await this.stopPipeline();
+                if (!stopResult.success) {
+                    return;
                 }
-                savePipeline$ = this.pipelineService.updatePipeline(
-                    this.pipeline,
-                );
             }
-        }
 
-        this.performStorageOperations(stopPipeline$, savePipeline$);
+            const saveMessage = await this.storeOrUpdatePipeline();
+            if (!saveMessage.success) {
+                this.handleStorageError();
+                return;
+            }
+
+            this.pipelineId = this.getPipelineId(saveMessage);
+            if (!this.pipelineId) {
+                this.handleStorageError();
+                return;
+            }
+
+            this.pipeline._id = this.pipelineId;
+            await this.savePipelineCanvasMetadata();
+            if (!(await this.startPipelineIfRequested())) {
+                return;
+            }
+            this.onSuccess();
+        } catch {
+            this.onFailure();
+        }
     }
 
-    shouldPerformUpdatePreflight(skipPreflight: boolean): boolean {
-        return (
-            !skipPreflight &&
-            this.storageOptions.updateModeActive &&
-            this.storageOptions.updateMode !== 'clone' &&
-            this.hasDataLakeSink()
-        );
+    private async shouldPerformUpdatePreflight(
+        skipPreflight: boolean,
+    ): Promise<boolean> {
+        return !skipPreflight && this.updateExisting && this.hasDataLakeSink();
     }
 
-    hasDataLakeSink(): boolean {
+    private hasDataLakeSink(): boolean {
         return this.pipeline.actions.some(
             action =>
                 action.appId ===
@@ -246,122 +156,131 @@ export class SavePipelineComponent implements OnInit {
         );
     }
 
-    performUpdatePreflight(): void {
+    private async performUpdatePreflight(): Promise<void> {
         this.operationProgress = true;
         this.addStatusIndicator(
             this.translateService.instant('Checking pipeline update'),
             Status.PROGRESS,
         );
-        this.pipelineService
-            .performPipelineMigrationPreflight(this.pipeline)
-            .subscribe({
-                next: updateInfos => {
-                    if (updateInfos.length === 0) {
-                        this.modifyStatusIndicator(Status.SUCCESS);
-                        this.savePipeline(true);
-                    } else {
-                        this.measurementUpdateInfos = updateInfos;
-                        this.pipelineUpdatePreflight = true;
-                        this.operationProgress = false;
-                        this.statusIndicators = [];
-                    }
-                },
-                error: msg => {
-                    this.onFailure(msg);
-                },
-            });
-    }
-
-    updateId(entity: InvocablePipelineElementUnion) {
-        const lastIdIndex = entity.elementId.lastIndexOf(':');
-        entity.elementId =
-            entity.elementId.substring(0, lastIdIndex + 1) +
-            this.idGeneratorService.generate(5);
-    }
 
-    getStopPipeline$(): Observable<PipelineOperationStatus> {
-        return of(null).pipe(
-            tap(() =>
-                this.addStatusIndicator(
-                    this.translateService.instant('Stopping pipeline'),
-                    Status.PROGRESS,
+        try {
+            const updateInfos = await firstValueFrom(
+                this.pipelineService.performPipelineMigrationPreflight(
+                    this.pipeline,
                 ),
-            ),
-            switchMap(() =>
-                this.pipelineService.stopPipeline(this.originalPipeline._id),
-            ),
-            tap(msg => {
-                this.operationSuccess = msg.success;
-                if (!msg.success) {
-                    this.handlePipelineOperationError(msg, 
PipelineAction.Stop);
-                } else {
-                    this.modifyStatusIndicator(Status.SUCCESS);
-                }
-            }),
-            filter(status => status.success),
-        );
+            );
+
+            if (updateInfos.length === 0) {
+                this.modifyStatusIndicator(Status.SUCCESS);
+                await this.savePipeline(true);
+            } else {
+                this.measurementUpdateInfos = updateInfos;
+                this.pipelineUpdatePreflight = true;
+                this.operationProgress = false;
+                this.statusIndicators = [];
+            }
+        } catch {
+            this.onFailure();
+        }
     }
 
-    getStartPipeline$(): Observable<null | PipelineOperationStatus> {
-        if (this.storageOptions.startPipelineAfterStorage) {
-            return of(null).pipe(
-                tap(() =>
-                    this.addStatusIndicator(
-                        this.translateService.instant('Starting pipeline'),
-                        Status.PROGRESS,
-                    ),
-                ),
-                switchMap(() =>
-                    this.pipelineService.startPipeline(this.pipelineId),
-                ),
-                tap(msg => {
-                    if (!msg.success) {
-                        this.handlePipelineOperationError(
-                            msg,
-                            PipelineAction.Start,
-                        );
-                    } else {
-                        this.modifyStatusIndicator(
-                            msg.success ? Status.SUCCESS : Status.FAILURE,
-                        );
-                    }
-                }),
-            );
+    private async stopPipeline(): Promise<PipelineOperationStatus> {
+        this.addStatusIndicator(
+            this.translateService.instant('Stopping pipeline'),
+            Status.PROGRESS,
+        );
+        const stopResult = await firstValueFrom(
+            this.pipelineService.stopPipeline(this.originalPipeline._id),
+        );
+        this.operationSuccess = stopResult.success;
+        if (!stopResult.success) {
+            this.handlePipelineOperationError(stopResult, PipelineAction.Stop);
         } else {
-            return of(null);
+            this.modifyStatusIndicator(Status.SUCCESS);
         }
+        return stopResult;
     }
 
-    getPipelineCanvasMetadata$(pipelineId: string): Observable<object> {
-        this.pipelineCanvasMetadata.pipelineId = pipelineId;
-        return this.pipelineCanvasService.updatePipelineCanvasMetadata(
-            pipelineId,
-            this.pipelineCanvasMetadata,
+    private async storeOrUpdatePipeline(): Promise<Message> {
+        this.addStatusIndicator(
+            this.translateService.instant('Saving pipeline'),
+            Status.PROGRESS,
         );
+        const saveMessage = this.updateExisting
+            ? await firstValueFrom(
+                  this.pipelineService.updatePipeline(this.pipeline),
+              )
+            : await firstValueFrom(
+                  this.pipelineService.storePipeline(this.pipeline),
+              );
+        this.operationSuccess = saveMessage.success;
+        this.modifyStatusIndicator(
+            saveMessage.success ? Status.SUCCESS : Status.FAILURE,
+        );
+        return saveMessage;
     }
 
-    addStatusIndicator(message: string, status: Status) {
-        this.statusIndicators.push({ message, status });
+    private getPipelineId(saveMessage: Message): string {
+        return (
+            (this.updateExisting ? this.originalPipeline?._id : undefined) ??
+            saveMessage.notifications?.[1]?.description
+        );
     }
 
-    modifyStatusIndicator(status: Status) {
-        // modify status of the last indicator
-        this.statusIndicators[this.statusIndicators.length - 1].status = 
status;
+    private async savePipelineCanvasMetadata(): Promise<void> {
+        this.addStatusIndicator(
+            this.translateService.instant('Saving metadata'),
+            Status.PROGRESS,
+        );
+        this.pipelineCanvasMetadata.pipelineId = this.pipelineId;
+        await firstValueFrom(
+            this.pipelineCanvasService.updatePipelineCanvasMetadata(
+                this.pipelineId,
+                this.pipelineCanvasMetadata,
+            ),
+        );
+        this.modifyStatusIndicator(Status.SUCCESS);
     }
 
-    handleStorageError(): void {
+    private async startPipelineIfRequested(): Promise<boolean> {
+        if (!this.startPipelineAfterStorage) {
+            return true;
+        }
+
+        this.addStatusIndicator(
+            this.translateService.instant('Starting pipeline'),
+            Status.PROGRESS,
+        );
+        const startResult = await firstValueFrom(
+            this.pipelineService.startPipeline(this.pipelineId),
+        );
+        if (!startResult.success) {
+            this.handlePipelineOperationError(
+                startResult,
+                PipelineAction.Start,
+            );
+            return false;
+        } else {
+            this.modifyStatusIndicator(Status.SUCCESS);
+            this.showPipelineOperationStatus(startResult, 
PipelineAction.Start);
+        }
+        return true;
+    }
+
+    private handleStorageError(): void {
         this.onFailure();
     }
 
-    handlePipelineOperationError(
+    private handlePipelineOperationError(
         status: PipelineOperationStatus,
         pipelineAction: PipelineAction,
-    ) {
+    ): void {
         this.onFailure();
         this.showPipelineOperationStatus(status, pipelineAction);
     }
 
-    onFailure(_msg?: any) {
+    private onFailure(): void {
+        this.operationProgress = false;
         this.operationCompleted = true;
         this.operationSuccess = false;
         if (this.statusIndicators.length > 0) {
@@ -369,97 +288,32 @@ export class SavePipelineComponent implements OnInit {
         }
     }
 
-    showPipelineOperationStatus(
+    private showPipelineOperationStatus(
         status: PipelineOperationStatus,
         pipelineAction: PipelineAction,
-    ) {
+    ): void {
         this.finalPipelineOperationStatus = status;
         this.pipelineAction = pipelineAction;
     }
 
-    onSuccess(status?: PipelineOperationStatus) {
+    private onSuccess(): void {
         this.operationProgress = false;
         this.operationCompleted = true;
-        if (status) {
-            this.showPipelineOperationStatus(status, PipelineAction.Start);
-        }
-        this.editorService.makePipelineAssemblyEmpty(true);
-        this.editorService.removePipelineFromCache().subscribe();
-        if (this.shepherdService.isTourActive()) {
-            this.shepherdService.hideCurrentStep();
-        }
-        if (this.storageOptions.navigateToPipelineOverview && status?.success) 
{
-            this.navigateToPipelineOverview();
-        }
+        this.operationSuccess = true;
     }
 
-    navigateToPipelineOverview(): void {
-        this.hide(true);
-        this.router.navigate(['pipelines']);
-    }
-
-    hide(skipReload: boolean) {
-        let reloadConfig = undefined;
-        if (!skipReload) {
-            reloadConfig = this.operationSuccess
-                ? { reload: true, pipelineId: this.pipelineId }
-                : undefined;
-        }
-        this.dialogRef.close(reloadConfig);
+    addStatusIndicator(message: string, status: Status): void {
+        this.statusIndicators.push({ message, status });
     }
 
-    async addToAsset(): Promise<void> {
-        let linkageData: LinkageData[] = [];
-        linkageData = await this.addPipelineLinkageData(linkageData);
-
-        await this.saveAssets(linkageData);
-    }
-    private async addPipelineLinkageData(
-        linkageData: LinkageData[],
-    ): Promise<LinkageData[]> {
-        const pipeline = await firstValueFrom(
-            this.pipelineService.getPipelineById(this.pipelineId),
-        );
-
-        linkageData.push({
-            type: 'pipeline',
-            id: this.pipelineId,
-            name: pipeline.name,
-        });
-
-        const serviceList: DataSinkInvocation[] =
-            pipeline.actions as DataSinkInvocation[];
-        const dataSinkServices: DataSinkInvocation[] = serviceList.filter(
-            action => action.serviceTagPrefix === 'DATA_SINK',
-        );
-
-        for (const service of dataSinkServices) {
-            const staticProperty = service.staticProperties.find(
-                prop => prop.internalName === 'db_measurement',
-            );
-
-            const measureFromPipeline = (staticProperty as { value: string })
-                .value;
-
-            const measure = await lastValueFrom(
-                this.dataLakeService.getMeasurementByName(measureFromPipeline),
-            );
-
-            linkageData.push({
-                type: 'measurement',
-                id: measure.elementId,
-                name: measureFromPipeline,
-            });
-        }
-        return linkageData;
+    modifyStatusIndicator(status: Status): void {
+        this.statusIndicators[this.statusIndicators.length - 1].status = 
status;
     }
 
-    private async saveAssets(linkageData: LinkageData[]): Promise<void> {
-        await this.assetSaveService.saveSelectedAssets(
-            this.selectedAssets,
-            linkageData,
-            this.deselectedAssets,
-            this.originalAssets,
-        );
+    close(): void {
+        this.dialogRef.close({
+            success: this.operationSuccess,
+            pipelineId: this.pipelineId,
+        } satisfies SavePipelineDialogResult);
     }
 }
diff --git a/ui/src/app/editor/editor.component.html 
b/ui/src/app/editor/editor.component.html
index 38a4d76a3c..69f45223c6 100644
--- a/ui/src/app/editor/editor.component.html
+++ b/ui/src/app/editor/editor.component.html
@@ -49,6 +49,7 @@
                     [rawPipelineModel]="rawPipelineModel"
                     [allElements]="allElements"
                     [originalPipeline]="originalPipeline"
+                    [cloneMode]="cloneMode"
                     [pipelineCanvasMetadata]="pipelineCanvasMetadata"
                     [pipelineCanvasMetadataAvailable]="
                         pipelineCanvasMetadataAvailable
diff --git a/ui/src/app/editor/editor.component.ts 
b/ui/src/app/editor/editor.component.ts
index 5a381a5342..b831c54554 100644
--- a/ui/src/app/editor/editor.component.ts
+++ b/ui/src/app/editor/editor.component.ts
@@ -78,6 +78,7 @@ export class EditorComponent implements OnInit {
 
     rawPipelineModel: PipelineElementConfig[] = [];
     originalPipeline: Pipeline;
+    cloneMode = false;
 
     allElementsLoaded = false;
     allMetadataLoaded = false;
@@ -86,6 +87,8 @@ export class EditorComponent implements OnInit {
 
     ngOnInit() {
         const pipelineId = this.activatedRoute.snapshot.params.pipelineId;
+        this.cloneMode =
+            this.activatedRoute.snapshot.queryParamMap.get('clone') === 'true';
         if (pipelineId) {
             this.loadPipelineToModify(pipelineId);
         } else {
@@ -153,7 +156,7 @@ export class EditorComponent implements OnInit {
                     this.breadcrumbService.updateBreadcrumb([
                         SpPipelineRoutes.BASE,
                         { label: this.originalPipeline.name },
-                        { label: 'Modify' },
+                        { label: this.cloneMode ? 'Clone' : 'Modify' },
                     ]);
                     this.rawPipelineModel = 
this.jsplumbService.makeRawPipeline(
                         this.originalPipeline,
diff --git 
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
 
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
index 4fb0cb6d3f..5c9aec51d0 100644
--- 
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
+++ 
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
@@ -215,20 +215,40 @@
                 <mat-icon>mode_edit</mat-icon>
                 <span>{{ 'Edit' | translate }}</span>
             </button>
+            <button
+                mat-menu-item
+                (click)="
+                    pipelineOperationsService.clonePipeline(element.elementId)
+                "
+                data-cy="clone-pipeline-btn"
+            >
+                <mat-icon>content_copy</mat-icon>
+                <span>{{ 'Clone' | translate }}</span>
+            </button>
         }
         <button
             mat-menu-item
             (click)="
-                pipelineOperationsService.showPermissionsDialog(
+                pipelineOperationsService.showManageDialog(
                     element,
                     refreshPipelinesEmitter
                 )
             "
-            data-cy="open-manage-permissions"
+            data-cy="open-manage-pipeline"
         >
             <mat-icon>share</mat-icon>
-            <span>{{ 'Manage permissions' | translate }}</span>
+            <span>{{ 'Manage' | translate }}</span>
         </button>
+        @if (hasPipelineWritePrivileges) {
+            <button
+                mat-menu-item
+                (click)="pipelineOperationsService.showCodeDialog(element)"
+                data-cy="open-pipeline-code-trigger"
+            >
+                <mat-icon>code</mat-icon>
+                <span>{{ 'Code' | translate }}</span>
+            </button>
+        }
         @if (hasPipelineWritePrivileges) {
             <button
                 mat-menu-item
diff --git a/ui/src/app/pipelines/services/pipeline-operations.service.ts 
b/ui/src/app/pipelines/services/pipeline-operations.service.ts
index 1a2e0fa37b..a07817169c 100644
--- a/ui/src/app/pipelines/services/pipeline-operations.service.ts
+++ b/ui/src/app/pipelines/services/pipeline-operations.service.ts
@@ -17,10 +17,17 @@
  */
 
 import { EventEmitter, inject, Injectable } from '@angular/core';
-import { PipelineSummaryDto } from '@streampipes/platform-services';
+import {
+    Message,
+    Pipeline,
+    PipelineService,
+    PipelineSummaryDto,
+} from '@streampipes/platform-services';
 import {
     DialogRef,
     DialogService,
+    ObjectManageDialogComponent,
+    ObjectManageDialogResourceConfig,
     ObjectPermissionDialogComponent,
     PanelType,
 } from '@streampipes/shared-ui';
@@ -29,11 +36,14 @@ import { DeletePipelineDialogComponent } from 
'../dialog/delete-pipeline/delete-
 import { Router } from '@angular/router';
 import { PipelineAction } from '../model/pipeline-model';
 import { PipelineNotificationsComponent } from 
'../dialog/pipeline-notifications/pipeline-notifications.component';
+import { PipelineCodeDialogComponent } from 
'../../pipeline-details/dialogs/pipeline-code/pipeline-code-dialog.component';
+import { firstValueFrom } from 'rxjs';
 
 @Injectable({ providedIn: 'root' })
 export class PipelineOperationsService {
     private dialogService = inject(DialogService);
     private router = inject(Router);
+    private pipelineService = inject(PipelineService);
 
     starting: any;
     stopping: any;
@@ -156,6 +166,77 @@ export class PipelineOperationsService {
         });
     }
 
+    showManageDialog(
+        pipelineSummary: PipelineSummaryDto,
+        refreshPipelinesEmitter: EventEmitter<boolean>,
+    ) {
+        this.pipelineService
+            .getPipelineById(pipelineSummary.elementId)
+            .subscribe(pipeline => {
+                const resourceConfig: 
ObjectManageDialogResourceConfig<Pipeline> =
+                    {
+                        resourceLabel: 'Pipeline',
+                        nameLabel: 'Pipeline name',
+                        descriptionLabel: 'Description',
+                        nameProperty: 'name',
+                        assetLinkType: 'pipeline',
+                        assetLinkCheckboxLabel:
+                            'Add the current pipeline to an existing asset',
+                        saveResource: async resource => {
+                            const shouldRestart = resource.running;
+
+                            if (shouldRestart) {
+                                const stopResult = await firstValueFrom(
+                                    this.pipelineService.stopPipeline(
+                                        resource._id,
+                                    ),
+                                );
+                                this.assertPipelineOperationSucceeded(
+                                    stopResult.success,
+                                    'Stopping the pipeline failed.',
+                                );
+                            }
+
+                            const result = await firstValueFrom(
+                                this.pipelineService.updatePipeline(resource),
+                            );
+                            this.assertPipelineSaveSucceeded(result);
+
+                            if (shouldRestart) {
+                                const startResult = await firstValueFrom(
+                                    this.pipelineService.startPipeline(
+                                        resource._id,
+                                    ),
+                                );
+                                this.assertPipelineOperationSucceeded(
+                                    startResult.success,
+                                    'Starting the pipeline failed.',
+                                );
+                            }
+                        },
+                    };
+                const dialogRef = this.dialogService.open(
+                    ObjectManageDialogComponent,
+                    {
+                        panelType: PanelType.SLIDE_IN_PANEL,
+                        title: 'Manage',
+                        width: '50vw',
+                        data: {
+                            objectInstanceId: pipeline._id,
+                            resource: { ...pipeline },
+                            saveMode: 'immediate',
+                            resourceConfig,
+                            headerTitle: 'Manage Pipeline ' + pipeline.name,
+                        },
+                    },
+                );
+
+                dialogRef.afterClosed().subscribe(refresh => {
+                    refreshPipelinesEmitter.emit(!!refresh);
+                });
+            });
+    }
+
     showPermissionsDialog(
         pipelineSummary: PipelineSummaryDto,
         refreshPipelinesEmitter: EventEmitter<boolean>,
@@ -180,15 +261,55 @@ export class PipelineOperationsService {
         });
     }
 
+    showCodeDialog(pipelineSummary: PipelineSummaryDto): void {
+        this.pipelineService
+            .getPipelineById(pipelineSummary.elementId)
+            .subscribe(pipeline => {
+                this.dialogService.open(PipelineCodeDialogComponent, {
+                    panelType: PanelType.SLIDE_IN_PANEL,
+                    width: '50vw',
+                    title: 'Pipeline code',
+                    data: {
+                        pipeline,
+                    },
+                });
+            });
+    }
+
     showPipelineInEditor(id: string) {
         this.router.navigate(['pipelines', 'modify', id]);
     }
 
+    showPipelineCloneInEditor(id: string) {
+        this.router.navigate(['pipelines', 'modify', id], {
+            queryParams: { clone: true },
+        });
+    }
+
     showPipelineDetails(id: string) {
         this.router.navigate(['pipelines', 'details', id]);
     }
 
-    modifyPipeline(pipeline) {
-        this.showPipelineInEditor(pipeline);
+    modifyPipeline(pipelineId: string) {
+        this.showPipelineInEditor(pipelineId);
+    }
+
+    clonePipeline(pipelineId: string) {
+        this.showPipelineCloneInEditor(pipelineId);
+    }
+
+    private assertPipelineSaveSucceeded(result: Message): void {
+        if (!result.success) {
+            throw new Error('Saving the pipeline failed.');
+        }
+    }
+
+    private assertPipelineOperationSucceeded(
+        success: boolean,
+        errorMessage: string,
+    ): void {
+        if (!success) {
+            throw new Error(errorMessage);
+        }
     }
 }

Reply via email to