You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by wi...@apache.org on 2021/01/08 07:54:33 UTC

[incubator-streampipes] branch edge-extensions updated (b94f09b -> 5a71414)

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

wiener pushed a change to branch edge-extensions
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git.


    from b94f09b  [WIP] add ui part for processor live-migration, add backend migration endpoint
     add bba1ac8  [STREAMPIPES-268] Bump Spring Boot version to 2.4.1 and Spring version to 5.3.2
     new 781ddec  Merge branch 'dev' into edge-extensions
     new 5a71414  [WIP] store node description in couchdb, add CRUD operation on description, extent frontend node configuration view

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 pom.xml                                            |   4 +-
 streampipes-backend/src/main/resources/shiro.ini   |   2 +
 .../extensions/ExtensionsModelSubmitter.java       |   1 +
 .../streampipes/container/util/ConsulUtil.java     |   2 +-
 .../model/node/NodeBrokerDescription.java          |   4 +-
 .../model/node/NodeInfoDescription.java            |  51 ++++++-
 .../model/node/NodeInfoDescriptionBuilder.java     |  11 +-
 .../model/node/NodeResourceBuilder.java            |  11 +-
 .../model/node/container/DeploymentContainer.java  |   6 +-
 .../model/node/container/DockerContainer.java      |   1 +
 ...aticNodeMedata.java => StaticNodeMetadata.java} |  12 +-
 .../node/resources/software/ContainerRuntime.java  |   4 +
 .../controller/container/NodeControllerInit.java   |   1 -
 .../container/management/node/NodeManager.java     |  41 ++++-
 .../management/pe/InvocableElementManager.java     |  44 ++++++
 .../container/rest/InfoStatusResource.java         |  18 ++-
 .../manager/node/NodeClusterManager.java           |  84 ++++++++++
 .../streampipes/manager/operations/Operations.java |  38 ++++-
 .../org/apache/streampipes/rest/api/INode.java     |  10 ++
 .../org/apache/streampipes/rest/api/IPipeline.java |   2 +
 .../org/apache/streampipes/rest/impl/Node.java     |  52 ++++++-
 .../rest/impl/PipelineWithUserResource.java        |   2 +
 .../builder/AbstractProcessingElementBuilder.java  |   4 +
 .../sdk/helpers/CollectedResourceRequirements.java |   4 +-
 .../serializers/json/GsonSerializer.java           |  13 ++
 .../jsonld/CustomAnnotationProvider.java           |  28 +++-
 .../serializers/jsonld/JsonLdTransformer.java      |   5 +-
 .../streampipes/storage/api/INoSqlStorage.java     |   1 +
 .../{ILabelStorage.java => INodeInfoStorage.java}  |  20 ++-
 .../storage/couchdb/CouchDbStorageManager.java     |   5 +
 .../storage/couchdb/impl/NodeInfoStorageImpl.java  |  87 +++++++++++
 .../streampipes/storage/couchdb/utils/Utils.java   |   6 +
 ui/package.json                                    |   4 +-
 .../dashboard-overview.component.css               |   2 +-
 .../app/app-container/app-container.component.html |   2 +-
 .../app/app-overview/app-overview.component.html   |   2 +-
 .../app/configuration/configuration.component.html |   2 +-
 ui/src/app/configuration/configuration.module.ts   |  12 +-
 .../datalake-configuration.component.html          |   2 +-
 .../edge-configuration.component.html              |  77 ----------
 .../edge-configuration.component.ts                |  92 -----------
 .../node-configuration-details.component.html      |  61 ++++++++
 .../node-configuration-details.component.scss}     |   0
 .../node-configuration-details.component.ts        |  90 +++++++++++
 .../node-configuration.component.html              | 130 ++++++++++++++++
 .../node-configuration.component.scss}             |  60 +++++++-
 .../node-configuration.component.ts                | 169 +++++++++++++++++++++
 .../configuration/shared/configuration.service.ts  |   9 --
 ui/src/app/core-model/gen/streampipes-model.ts     |  26 ++--
 .../pipeline-assembly.component.ts                 |   4 +
 .../migrate-pipeline-processors.component.ts       |  14 +-
 .../save-pipeline/save-pipeline.component.ts       |  34 +++--
 .../app/editor/services/object-provider.service.ts |   1 +
 ui/src/app/platform-services/apis/node.service.ts  |  55 +++++++
 .../app/platform-services/apis/pipeline.service.ts |   8 -
 ui/src/app/platform-services/platform.module.ts    |   4 +-
 56 files changed, 1153 insertions(+), 281 deletions(-)
 rename streampipes-model/src/main/java/org/apache/streampipes/model/node/meta/{StaticNodeMedata.java => StaticNodeMetadata.java} (85%)
 create mode 100644 streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/node/NodeClusterManager.java
 copy streampipes-connect/src/main/java/org/apache/streampipes/connect/adapter/model/Connector.java => streampipes-sdk/src/main/java/org/apache/streampipes/sdk/helpers/CollectedResourceRequirements.java (90%)
 copy streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/{ILabelStorage.java => INodeInfoStorage.java} (67%)
 create mode 100644 streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/NodeInfoStorageImpl.java
 delete mode 100644 ui/src/app/configuration/edge-configuration/edge-configuration.component.html
 delete mode 100644 ui/src/app/configuration/edge-configuration/edge-configuration.component.ts
 create mode 100644 ui/src/app/configuration/node-configuration/node-configuration-details/node-configuration-details.component.html
 copy ui/src/app/{editor/dialog/migrate-pipeline-processors/migrate-pipeline-processors.component.scss => configuration/node-configuration/node-configuration-details/node-configuration-details.component.scss} (100%)
 create mode 100644 ui/src/app/configuration/node-configuration/node-configuration-details/node-configuration-details.component.ts
 create mode 100644 ui/src/app/configuration/node-configuration/node-configuration.component.html
 rename ui/src/app/configuration/{edge-configuration/edge-configuration.component.css => node-configuration/node-configuration.component.scss} (70%)
 create mode 100644 ui/src/app/configuration/node-configuration/node-configuration.component.ts
 create mode 100644 ui/src/app/platform-services/apis/node.service.ts


[incubator-streampipes] 01/02: Merge branch 'dev' into edge-extensions

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wiener pushed a commit to branch edge-extensions
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit 781ddec21995f7c8ae4e9e2f83bc4170c4e17b02
Merge: b94f09b bba1ac8
Author: Patrick Wiener <wi...@fzi.de>
AuthorDate: Wed Dec 30 10:38:17 2020 +0100

    Merge branch 'dev' into edge-extensions

 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)



[incubator-streampipes] 02/02: [WIP] store node description in couchdb, add CRUD operation on description, extent frontend node configuration view

Posted by wi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wiener pushed a commit to branch edge-extensions
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit 5a714149fb30e5d9746fda69c6613860768be25a
Author: Patrick Wiener <wi...@fzi.de>
AuthorDate: Fri Jan 8 08:51:29 2021 +0100

    [WIP] store node description in couchdb, add CRUD operation on description, extent frontend node configuration view
---
 streampipes-backend/src/main/resources/shiro.ini   |   2 +
 .../extensions/ExtensionsModelSubmitter.java       |   1 +
 .../streampipes/container/util/ConsulUtil.java     |   2 +-
 .../model/node/NodeBrokerDescription.java          |   4 +-
 .../model/node/NodeInfoDescription.java            |  51 ++++++-
 .../model/node/NodeInfoDescriptionBuilder.java     |  11 +-
 .../model/node/NodeResourceBuilder.java            |  11 +-
 .../model/node/container/DeploymentContainer.java  |   6 +-
 .../model/node/container/DockerContainer.java      |   1 +
 ...aticNodeMedata.java => StaticNodeMetadata.java} |  12 +-
 .../node/resources/software/ContainerRuntime.java  |   4 +
 .../controller/container/NodeControllerInit.java   |   1 -
 .../container/management/node/NodeManager.java     |  41 ++++-
 .../management/pe/InvocableElementManager.java     |  44 ++++++
 .../container/rest/InfoStatusResource.java         |  18 ++-
 .../manager/node/NodeClusterManager.java           |  84 ++++++++++
 .../streampipes/manager/operations/Operations.java |  38 ++++-
 .../org/apache/streampipes/rest/api/INode.java     |  10 ++
 .../org/apache/streampipes/rest/api/IPipeline.java |   2 +
 .../org/apache/streampipes/rest/impl/Node.java     |  52 ++++++-
 .../rest/impl/PipelineWithUserResource.java        |   2 +
 .../builder/AbstractProcessingElementBuilder.java  |   4 +
 .../sdk/helpers/CollectedResourceRequirements.java |   8 +-
 .../serializers/json/GsonSerializer.java           |  13 ++
 .../jsonld/CustomAnnotationProvider.java           |  28 +++-
 .../serializers/jsonld/JsonLdTransformer.java      |   5 +-
 .../streampipes/storage/api/INoSqlStorage.java     |   1 +
 .../streampipes/storage/api/INodeInfoStorage.java  |  22 ++-
 .../storage/couchdb/CouchDbStorageManager.java     |   5 +
 .../storage/couchdb/impl/NodeInfoStorageImpl.java  |  87 +++++++++++
 .../streampipes/storage/couchdb/utils/Utils.java   |   6 +
 ui/package.json                                    |   4 +-
 .../dashboard-overview.component.css               |   2 +-
 .../app/app-container/app-container.component.html |   2 +-
 .../app/app-overview/app-overview.component.html   |   2 +-
 .../app/configuration/configuration.component.html |   2 +-
 ui/src/app/configuration/configuration.module.ts   |  12 +-
 .../datalake-configuration.component.html          |   2 +-
 .../edge-configuration.component.html              |  77 ----------
 .../edge-configuration.component.ts                |  92 -----------
 .../node-configuration-details.component.html      |  61 ++++++++
 .../node-configuration-details.component.scss      |  98 ++++++++++++
 .../node-configuration-details.component.ts        |  90 +++++++++++
 .../node-configuration.component.html              | 130 ++++++++++++++++
 .../node-configuration.component.scss}             |  60 +++++++-
 .../node-configuration.component.ts                | 169 +++++++++++++++++++++
 .../configuration/shared/configuration.service.ts  |   9 --
 ui/src/app/core-model/gen/streampipes-model.ts     |  26 ++--
 .../pipeline-assembly.component.ts                 |   4 +
 .../migrate-pipeline-processors.component.ts       |  14 +-
 .../save-pipeline/save-pipeline.component.ts       |  34 +++--
 .../app/editor/services/object-provider.service.ts |   1 +
 ui/src/app/platform-services/apis/node.service.ts  |  55 +++++++
 .../app/platform-services/apis/pipeline.service.ts |   8 -
 ui/src/app/platform-services/platform.module.ts    |   4 +-
 55 files changed, 1258 insertions(+), 276 deletions(-)

diff --git a/streampipes-backend/src/main/resources/shiro.ini b/streampipes-backend/src/main/resources/shiro.ini
index f6f60c6..c361090 100644
--- a/streampipes-backend/src/main/resources/shiro.ini
+++ b/streampipes-backend/src/main/resources/shiro.ini
@@ -67,6 +67,8 @@ securityManager.rememberMeManager.cookie.maxAge = 1000000000
 /api/v2/users/*/labeling/category/* = anon
 /api/v2/users/*/labeling/* = anon
 /api/v2/users/*/labeling = anon
+/api/v2/users/*/nodes = anon
+/api/v2/users/*/nodes/* = anon
 /api/v2/connect/*/master/administration = anon
 /api/v2/connect/*/master/sources/* = anon
 /api/v2/connect/*/master/sources/*/streams = anon
diff --git a/streampipes-container-extensions/src/main/java/org/apache/streampipes/container/extensions/ExtensionsModelSubmitter.java b/streampipes-container-extensions/src/main/java/org/apache/streampipes/container/extensions/ExtensionsModelSubmitter.java
index 1e9a9b7..967519c 100644
--- a/streampipes-container-extensions/src/main/java/org/apache/streampipes/container/extensions/ExtensionsModelSubmitter.java
+++ b/streampipes-container-extensions/src/main/java/org/apache/streampipes/container/extensions/ExtensionsModelSubmitter.java
@@ -17,6 +17,7 @@
  */
 package org.apache.streampipes.container.extensions;
 
+import io.undertow.server.handlers.proxy.mod_cluster.NodeConfig;
 import org.apache.streampipes.connect.adapter.Adapter;
 import org.apache.streampipes.connect.adapter.model.generic.Protocol;
 import org.apache.streampipes.connect.container.worker.management.MasterRestClient;
diff --git a/streampipes-container/src/main/java/org/apache/streampipes/container/util/ConsulUtil.java b/streampipes-container/src/main/java/org/apache/streampipes/container/util/ConsulUtil.java
index a2f1b45..abe4094 100644
--- a/streampipes-container/src/main/java/org/apache/streampipes/container/util/ConsulUtil.java
+++ b/streampipes-container/src/main/java/org/apache/streampipes/container/util/ConsulUtil.java
@@ -85,7 +85,7 @@ public class ConsulUtil {
   }
 
   /**
-   * Method called by {@link org.apache.streampipes.node.controller.container.NodeControllerContainerInit} to
+   * Method called by {@link org.apache.streampipes.node.controller.container.NodeControllerInit} to
    * register new node controller service endpoint.
    *
    * @param svcId unique service id
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeBrokerDescription.java b/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeBrokerDescription.java
index 1c925be..2d4d29b 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeBrokerDescription.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeBrokerDescription.java
@@ -24,13 +24,15 @@ import org.apache.streampipes.model.grounding.TransportProtocol;
 import org.apache.streampipes.model.shared.annotation.TsModel;
 import org.apache.streampipes.vocabulary.StreamPipes;
 
-import javax.persistence.Entity;
+import javax.persistence.*;
 
 @RdfsClass(StreamPipes.NODE_BROKER_DESCRIPTION)
 @Entity
 @TsModel
 public class NodeBrokerDescription extends UnnamedStreamPipesEntity {
 
+    @OneToOne(fetch = FetchType.EAGER,
+            cascade = {CascadeType.ALL})
     @RdfProperty(StreamPipes.HAS_TRANSPORT_PROTOCOL)
     private TransportProtocol nodeTransportProtocol;
 
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeInfoDescription.java b/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeInfoDescription.java
index 0dcf898..1d87f86 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeInfoDescription.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeInfoDescription.java
@@ -17,11 +17,13 @@
  */
 package org.apache.streampipes.model.node;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
 import io.fogsy.empire.annotations.RdfProperty;
 import io.fogsy.empire.annotations.RdfsClass;
 import org.apache.streampipes.model.base.UnnamedStreamPipesEntity;
 import org.apache.streampipes.model.node.container.DeploymentContainer;
-import org.apache.streampipes.model.node.meta.StaticNodeMedata;
+import org.apache.streampipes.model.node.meta.StaticNodeMetadata;
 import org.apache.streampipes.model.node.resources.NodeResource;
 import org.apache.streampipes.model.shared.annotation.TsModel;
 import org.apache.streampipes.vocabulary.StreamPipes;
@@ -34,6 +36,15 @@ import java.util.List;
 @TsModel
 public class NodeInfoDescription extends UnnamedStreamPipesEntity {
 
+    @JsonProperty("_id")
+    private @SerializedName("_id") String id;
+
+    @JsonProperty("_rev")
+    private @SerializedName("_rev") String rev;
+
+    @OneToOne(cascade = CascadeType.ALL)
+    private boolean active;
+
     @RdfProperty(StreamPipes.DEPLOYMENT_TARGET_NODE_ID)
     private String nodeControllerId;
 
@@ -51,7 +62,7 @@ public class NodeInfoDescription extends UnnamedStreamPipesEntity {
     @OneToOne(fetch = FetchType.EAGER,
             cascade = {CascadeType.ALL})
     @RdfProperty(StreamPipes.HAS_STATIC_NODE_METADATA)
-    private StaticNodeMedata staticNodeMedata;
+    private StaticNodeMetadata staticNodeMetadata;
 
     @OneToOne(fetch = FetchType.EAGER,
             cascade = {CascadeType.ALL})
@@ -75,6 +86,30 @@ public class NodeInfoDescription extends UnnamedStreamPipesEntity {
         this.nodeControllerId = elementId;
     }
 
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getRev() {
+        return rev;
+    }
+
+    public void setRev(String rev) {
+        this.rev = rev;
+    }
+
+    public boolean isActive() {
+        return active;
+    }
+
+    public void setActive(boolean active) {
+        this.active = active;
+    }
+
     public NodeBrokerDescription getNodeBroker() {
         return nodeBroker;
     }
@@ -83,19 +118,19 @@ public class NodeInfoDescription extends UnnamedStreamPipesEntity {
         this.nodeBroker = nodeBroker;
     }
 
-    public StaticNodeMedata getStaticNodeMedata() {
-        return staticNodeMedata;
+    public StaticNodeMetadata getStaticNodeMetadata() {
+        return staticNodeMetadata;
     }
 
-    public void setStaticNodeMedata(StaticNodeMedata staticNodeMedata) {
-        this.staticNodeMedata = staticNodeMedata;
+    public void setStaticNodeMetadata(StaticNodeMetadata staticNodeMetadata) {
+        this.staticNodeMetadata = staticNodeMetadata;
     }
 
-    public NodeResource getNodeResource() {
+    public NodeResource getNodeResources() {
         return nodeResources;
     }
 
-    public void setNodeResource(NodeResource nodeResources) {
+    public void setNodeResources(NodeResource nodeResources) {
         this.nodeResources = nodeResources;
     }
 
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeInfoDescriptionBuilder.java b/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeInfoDescriptionBuilder.java
index db3c1ae..e8d0729 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeInfoDescriptionBuilder.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeInfoDescriptionBuilder.java
@@ -21,7 +21,7 @@ import org.apache.streampipes.model.grounding.MqttTransportProtocol;
 import org.apache.streampipes.model.grounding.TransportProtocol;
 import org.apache.streampipes.model.node.container.DeploymentContainer;
 import org.apache.streampipes.model.node.meta.GeoLocation;
-import org.apache.streampipes.model.node.meta.StaticNodeMedata;
+import org.apache.streampipes.model.node.meta.StaticNodeMetadata;
 import org.apache.streampipes.model.node.resources.NodeResource;
 
 import java.util.ArrayList;
@@ -31,7 +31,7 @@ public class NodeInfoDescriptionBuilder {
 
     private final NodeInfoDescription nodeInfoDescription;
     private final NodeBrokerDescription nodeBroker;
-    private final StaticNodeMedata staticNodeMetadata;
+    private final StaticNodeMetadata staticNodeMetadata;
     private NodeResource nodeResources;
     private List<DeploymentContainer> registeredContainers;
     private final List<String> supportedElements;
@@ -39,7 +39,7 @@ public class NodeInfoDescriptionBuilder {
     public NodeInfoDescriptionBuilder(String id) {
         this.nodeInfoDescription = new NodeInfoDescription(id);
         this.nodeBroker = new NodeBrokerDescription();
-        this.staticNodeMetadata = new StaticNodeMedata();
+        this.staticNodeMetadata = new StaticNodeMetadata();
         this.nodeResources = new NodeResource();
         this.registeredContainers = new ArrayList<>();
         this.supportedElements = new ArrayList<>();
@@ -117,11 +117,12 @@ public class NodeInfoDescriptionBuilder {
     }
 
     public NodeInfoDescription build() {
-        nodeInfoDescription.setStaticNodeMedata(staticNodeMetadata);
+        nodeInfoDescription.setStaticNodeMetadata(staticNodeMetadata);
         nodeInfoDescription.setNodeBroker(nodeBroker);
         nodeInfoDescription.setRegisteredContainers(registeredContainers);
-        nodeInfoDescription.setNodeResource(nodeResources);
+        nodeInfoDescription.setNodeResources(nodeResources);
         nodeInfoDescription.setSupportedElements(supportedElements);
+        nodeInfoDescription.setActive(true);
         return nodeInfoDescription;
     }
 }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeResourceBuilder.java b/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeResourceBuilder.java
index e181be9..4fd26f8 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeResourceBuilder.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/node/NodeResourceBuilder.java
@@ -21,6 +21,8 @@ import org.apache.streampipes.model.node.resources.*;
 import org.apache.streampipes.model.node.resources.fielddevice.FieldDeviceAccessResource;
 import org.apache.streampipes.model.node.resources.hardware.*;
 import org.apache.streampipes.model.node.resources.software.ContainerRuntime;
+import org.apache.streampipes.model.node.resources.software.DockerContainerRuntime;
+import org.apache.streampipes.model.node.resources.software.NvidiaContainerRuntime;
 import org.apache.streampipes.model.node.resources.software.SoftwareResource;
 
 import java.util.ArrayList;
@@ -53,7 +55,14 @@ public class NodeResourceBuilder {
                                                 ContainerRuntime containerRuntime) {
         this.softwareResource.setOs(os);
         this.softwareResource.setKernelVersion(kernelVersion);
-        this.softwareResource.setContainerRuntime(containerRuntime);
+        if (containerRuntime instanceof DockerContainerRuntime) {
+            DockerContainerRuntime dcr = (DockerContainerRuntime) containerRuntime;
+            this.softwareResource.setContainerRuntime(dcr);
+        } else if (containerRuntime instanceof NvidiaContainerRuntime) {
+            NvidiaContainerRuntime ncr = (NvidiaContainerRuntime) containerRuntime;
+            this.softwareResource.setContainerRuntime(ncr);
+        }
+
         return this;
     }
 
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/node/container/DeploymentContainer.java b/streampipes-model/src/main/java/org/apache/streampipes/model/node/container/DeploymentContainer.java
index 31663c3..cd8f0b2 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/node/container/DeploymentContainer.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/node/container/DeploymentContainer.java
@@ -24,7 +24,7 @@ import org.apache.streampipes.model.base.UnnamedStreamPipesEntity;
 import org.apache.streampipes.model.shared.annotation.TsModel;
 import org.apache.streampipes.vocabulary.StreamPipes;
 
-import javax.persistence.Entity;
+import javax.persistence.*;
 import java.util.List;
 import java.util.Map;
 
@@ -48,9 +48,13 @@ public abstract class DeploymentContainer extends UnnamedStreamPipesEntity {
     @RdfProperty(StreamPipes.DEPLOYMENT_CONTAINER_PORTS)
     private String[] containerPorts;
 
+    @OneToMany(fetch = FetchType.EAGER,
+            cascade = {CascadeType.ALL})
     @RdfProperty(StreamPipes.DEPLOYMENT_CONTAINER_ENV_VARS)
     private List<String> envVars;
 
+    @OneToMany(fetch = FetchType.EAGER,
+            cascade = {CascadeType.ALL})
     @RdfProperty(StreamPipes.DEPLOYMENT_CONTAINER_LABELS)
     private Map<String, String> labels;
 
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/node/container/DockerContainer.java b/streampipes-model/src/main/java/org/apache/streampipes/model/node/container/DockerContainer.java
index 3173b20..3d97fe9 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/node/container/DockerContainer.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/node/container/DockerContainer.java
@@ -35,6 +35,7 @@ public class DockerContainer extends DeploymentContainer {
     }
 
     public DockerContainer() {
+        super();
     }
 
     public DockerContainer(String imageURI, String containerName, String serviceId, String[] containerPorts,
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/node/meta/StaticNodeMedata.java b/streampipes-model/src/main/java/org/apache/streampipes/model/node/meta/StaticNodeMetadata.java
similarity index 85%
rename from streampipes-model/src/main/java/org/apache/streampipes/model/node/meta/StaticNodeMedata.java
rename to streampipes-model/src/main/java/org/apache/streampipes/model/node/meta/StaticNodeMetadata.java
index 7a3fb41..8bed4b8 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/node/meta/StaticNodeMedata.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/node/meta/StaticNodeMetadata.java
@@ -23,14 +23,14 @@ import org.apache.streampipes.model.base.UnnamedStreamPipesEntity;
 import org.apache.streampipes.model.shared.annotation.TsModel;
 import org.apache.streampipes.vocabulary.StreamPipes;
 
-import javax.persistence.Entity;
+import javax.persistence.*;
 import java.util.ArrayList;
 import java.util.List;
 
 @RdfsClass(StreamPipes.STATIC_NODE_METADATA)
 @Entity
 @TsModel
-public class StaticNodeMedata extends UnnamedStreamPipesEntity {
+public class StaticNodeMetadata extends UnnamedStreamPipesEntity {
 
     @RdfProperty(StreamPipes.NODE_TYPE)
     private String type;
@@ -38,18 +38,22 @@ public class StaticNodeMedata extends UnnamedStreamPipesEntity {
     @RdfProperty(StreamPipes.NODE_MODEL)
     private String model;
 
+    @OneToOne(fetch = FetchType.EAGER,
+            cascade = {CascadeType.ALL})
     @RdfProperty(StreamPipes.HAS_GEO_LOCATION)
     private GeoLocation geoLocation;
 
+    @OneToMany(fetch = FetchType.EAGER,
+            cascade = {CascadeType.ALL})
     @RdfProperty(StreamPipes.HAS_NODE_TAGS)
     private List<String> locationTags;
 
-    public StaticNodeMedata() {
+    public StaticNodeMetadata() {
         super();
         this.locationTags = new ArrayList<>();
     }
 
-    public StaticNodeMedata(String type, String model, GeoLocation geoLocation, List<String> locationTags) {
+    public StaticNodeMetadata(String type, String model, GeoLocation geoLocation, List<String> locationTags) {
         super();
         this.type = type;
         this.model = model;
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/node/resources/software/ContainerRuntime.java b/streampipes-model/src/main/java/org/apache/streampipes/model/node/resources/software/ContainerRuntime.java
index f852d6b..0d52571 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/node/resources/software/ContainerRuntime.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/node/resources/software/ContainerRuntime.java
@@ -44,6 +44,10 @@ public abstract class ContainerRuntime extends UnnamedStreamPipesEntity {
         super();
     }
 
+    public ContainerRuntime(String elementId) {
+        super(elementId);
+    }
+
     public ContainerRuntime(ContainerRuntime other) {
         super(other);
     }
diff --git a/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/NodeControllerInit.java b/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/NodeControllerInit.java
index 6e61480..be6c385 100644
--- a/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/NodeControllerInit.java
+++ b/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/NodeControllerInit.java
@@ -75,5 +75,4 @@ public class NodeControllerInit {
     @PreDestroy
     public void onExit(){
     }
-
 }
diff --git a/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/management/node/NodeManager.java b/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/management/node/NodeManager.java
index ebc7261..c468440 100644
--- a/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/management/node/NodeManager.java
+++ b/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/management/node/NodeManager.java
@@ -17,11 +17,17 @@
  */
 package org.apache.streampipes.node.controller.container.management.node;
 
+import org.apache.http.client.fluent.Request;
+import org.apache.http.entity.ContentType;
 import org.apache.streampipes.model.node.*;
 import org.apache.streampipes.model.node.meta.GeoLocation;
+import org.apache.streampipes.node.controller.container.config.NodeControllerConfig;
+import org.apache.streampipes.serializers.json.JacksonSerializer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+
 public class NodeManager {
     private static final Logger LOG = LoggerFactory.getLogger(NodeManager.class.getCanonicalName());
 
@@ -58,8 +64,7 @@ public class NodeManager {
                                 NodeConstants.NODE_LOCATION_TAGS)
                         .withSupportedElements(NodeConstants.SUPPORTED_PIPELINE_ELEMENTS)
                         .withRegisteredContainers(NodeConstants.REGISTERED_DOCKER_CONTAINER)
-                        .withNodeResources(NodeResourceBuilder
-                                .create()
+                        .withNodeResources(NodeResourceBuilder.create()
                                 .hardwareResource(
                                         NodeConstants.NODE_CPU,
                                         NodeConstants.NODE_MEMORY,
@@ -74,5 +79,37 @@ public class NodeManager {
                         .build();
 
         NodeManager.getInstance().add(nodeInfoDescription);
+
+        register(nodeInfoDescription);
+    }
+
+    private void register(NodeInfoDescription desc) {
+
+        String url =
+                "http://"
+                + NodeControllerConfig.INSTANCE.getBackendHost()
+                + ":"
+                + NodeControllerConfig.INSTANCE.getBackendPort()
+                + "/"
+                + "streampipes-backend/api/v2/users/admin@streampipes.org/nodes";
+
+        try {
+            String nodeInfoDescription = JacksonSerializer.getObjectMapper().writeValueAsString(desc);
+
+            Request.Post(url)
+                    .bodyString(nodeInfoDescription, ContentType.APPLICATION_JSON)
+                    .connectTimeout(1000)
+                    .socketTimeout(100000)
+                    .execute().returnContent().asString();
+
+        } catch (IOException e) {
+            LOG.info("Could not connect to " + url);
+        }
+    }
+
+    public boolean updateNodeInfoDescription(NodeInfoDescription desc) {
+        LOG.info("Update node description for node controller: {}", desc.getNodeControllerId());
+        this.nodeInfo = desc;
+        return true;
     }
 }
diff --git a/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/management/pe/InvocableElementManager.java b/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/management/pe/InvocableElementManager.java
index 485f705..d89fddd 100644
--- a/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/management/pe/InvocableElementManager.java
+++ b/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/management/pe/InvocableElementManager.java
@@ -17,12 +17,15 @@
  */
 package org.apache.streampipes.node.controller.container.management.pe;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.gson.Gson;
+import org.apache.http.client.ClientProtocolException;
 import org.apache.http.client.fluent.Request;
 import org.apache.http.client.fluent.Response;
 import org.apache.http.entity.ContentType;
 import org.apache.http.entity.StringEntity;
 import org.apache.streampipes.container.model.node.InvocableRegistration;
+import org.apache.streampipes.node.controller.container.config.NodeControllerConfig;
 import org.apache.streampipes.node.controller.container.management.node.NodeManager;
 import org.apache.streampipes.serializers.json.JacksonSerializer;
 import org.slf4j.Logger;
@@ -70,6 +73,24 @@ public class InvocableElementManager implements InvocableLifeCycle {
                     .retrieveNodeInfoDescription()
                     .setSupportedElements(registration.getSupportedPipelineElementAppIds());
 
+            String url = "http://"
+                            + NodeControllerConfig.INSTANCE.getBackendHost()
+                            + ":"
+                            + NodeControllerConfig.INSTANCE.getBackendPort()
+                            + "/"
+                            + "streampipes-backend/api/v2/users/admin@streampipes.org/nodes"
+                            + "/"
+                            + NodeControllerConfig.INSTANCE.getNodeControllerId();
+
+            String desc = JacksonSerializer.getObjectMapper()
+                    .writeValueAsString(NodeManager.getInstance().retrieveNodeInfoDescription());
+
+            Request.Put(url)
+                    .bodyString(desc, ContentType.APPLICATION_JSON)
+//                    .connectTimeout(1000)
+//                    .socketTimeout(100000)
+                    .execute();
+
             LOG.info("Successfully registered pipeline element container");
         } catch (IOException e) {
             e.printStackTrace();
@@ -121,6 +142,29 @@ public class InvocableElementManager implements InvocableLifeCycle {
         NodeManager.getInstance()
                 .retrieveNodeInfoDescription()
                 .setSupportedElements(Collections.emptyList());
+
+        String url = "http://"
+                + NodeControllerConfig.INSTANCE.getBackendHost()
+                + ":"
+                + NodeControllerConfig.INSTANCE.getBackendPort()
+                + "/"
+                + "streampipes-backend/api/v2/users/admin@streampipes.org/nodes"
+                + "/"
+                + NodeControllerConfig.INSTANCE.getNodeControllerId();
+
+        try {
+            String desc = JacksonSerializer.getObjectMapper()
+                    .writeValueAsString(NodeManager.getInstance().retrieveNodeInfoDescription());
+
+            Request.Put(url)
+                    .bodyString(desc, ContentType.APPLICATION_JSON)
+                    .connectTimeout(1000)
+                    .socketTimeout(100000)
+                    .execute();
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
     }
 
     private String makeConsulRegistrationEndpoint() {
diff --git a/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/rest/InfoStatusResource.java b/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/rest/InfoStatusResource.java
index 24943fe..949d5e1 100644
--- a/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/rest/InfoStatusResource.java
+++ b/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/rest/InfoStatusResource.java
@@ -18,6 +18,7 @@
 package org.apache.streampipes.node.controller.container.rest;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.streampipes.model.node.NodeInfoDescription;
 import org.apache.streampipes.node.controller.container.management.node.NodeManager;
 import org.apache.streampipes.node.controller.container.management.relay.EventRelay;
 import org.apache.streampipes.node.controller.container.management.relay.RunningRelayInstances;
@@ -25,10 +26,10 @@ import org.apache.streampipes.node.controller.container.management.relay.metrics
 import org.apache.streampipes.node.controller.container.management.resource.ResourceManager;
 import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
 import org.apache.streampipes.serializers.json.JacksonSerializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
+import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.util.List;
@@ -36,14 +37,23 @@ import java.util.stream.Collectors;
 
 @Path("/api/v2/node")
 public class InfoStatusResource extends AbstractResource {
+    private static final Logger LOG = LoggerFactory.getLogger(InfoStatusResource.class.getCanonicalName());
 
     @GET
     @Path("/info")
     @Produces(MediaType.APPLICATION_JSON)
-    public Response getInfo() {
+    public Response getNodeInfo() {
         return ok(NodeManager.getInstance().retrieveNodeInfoDescription());
     }
 
+    @PUT
+    @Path("/update")
+    @JacksonSerialized
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response updateNodeInfo(NodeInfoDescription desc) {
+        return ok(NodeManager.getInstance().updateNodeInfoDescription(desc));
+    }
+
     @GET
     @Path("/status")
     @Produces(MediaType.APPLICATION_JSON)
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/node/NodeClusterManager.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/node/NodeClusterManager.java
new file mode 100644
index 0000000..c49a53f
--- /dev/null
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/node/NodeClusterManager.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.manager.node;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.fluent.Request;
+import org.apache.http.entity.ContentType;
+import org.apache.streampipes.model.node.NodeInfoDescription;
+import org.apache.streampipes.serializers.json.JacksonSerializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+public enum NodeClusterManager {
+    INSTANCE;
+
+
+    private static final Logger LOG =
+            LoggerFactory.getLogger(NodeClusterManager.class.getCanonicalName());
+
+
+    public boolean updateNodeInfoDescription(NodeInfoDescription desc) {
+        boolean successfullyUpdated = false;
+        try {
+            String body = JacksonSerializer.getObjectMapper().writeValueAsString(desc);
+            String url = makeNodeControllerEndpoint(desc);
+
+            LOG.info("Trying to update description for node controller: " + url);
+
+            boolean connected = false;
+            while (!connected) {
+                connected = put(url, body);
+
+                if (!connected) {
+                    LOG.info("Retrying in 5 seconds");
+                    try {
+                        Thread.sleep(5000);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+            successfullyUpdated = true;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return successfullyUpdated;
+    }
+
+    private String makeNodeControllerEndpoint(NodeInfoDescription desc) {
+        return "http://" + desc.getHostname() + ":" + desc.getPort() + "/api/v2/node/update";
+    }
+
+    private boolean put(String url, String body) {
+        try {
+            Request.Put(url)
+                    .bodyString(body, ContentType.APPLICATION_JSON)
+                    .connectTimeout(1000)
+                    .socketTimeout(100000)
+                    .execute();
+            return true;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/operations/Operations.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/operations/Operations.java
index 0853f70..eb71a42 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/operations/Operations.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/operations/Operations.java
@@ -27,6 +27,7 @@ import org.apache.streampipes.manager.execution.http.PipelineStorageService;
 import org.apache.streampipes.manager.matching.DataSetGroundingSelector;
 import org.apache.streampipes.manager.matching.PipelineVerificationHandler;
 import org.apache.streampipes.manager.node.AvailableNodesFetcher;
+import org.apache.streampipes.manager.node.NodeClusterManager;
 import org.apache.streampipes.manager.recommender.ElementRecommender;
 import org.apache.streampipes.manager.remote.ContainerProvidedOptionsHandler;
 import org.apache.streampipes.manager.runtime.PipelineElementRuntimeInfoFetcher;
@@ -39,8 +40,10 @@ import org.apache.streampipes.model.SpDataSet;
 import org.apache.streampipes.model.SpDataStream;
 import org.apache.streampipes.model.client.endpoint.RdfEndpoint;
 import org.apache.streampipes.model.client.endpoint.RdfEndpointItem;
+import org.apache.streampipes.model.connect.worker.ConnectWorkerContainer;
 import org.apache.streampipes.model.message.DataSetModificationMessage;
 import org.apache.streampipes.model.message.Message;
+import org.apache.streampipes.model.message.Notifications;
 import org.apache.streampipes.model.message.PipelineModificationMessage;
 import org.apache.streampipes.model.node.NodeInfoDescription;
 import org.apache.streampipes.model.pipeline.Pipeline;
@@ -187,6 +190,39 @@ public class Operations {
   }
 
   public static List<NodeInfoDescription> getAvailableNodes() {
-    return new AvailableNodesFetcher().fetchNodes();
+    //return new AvailableNodesFetcher().fetchNodes();
+    return StorageDispatcher.INSTANCE.getNoSqlStore().getNodeStorage().getAllActiveNodes();
+  }
+
+  public static List<NodeInfoDescription> getAllNodes() {
+    //return new AvailableNodesFetcher().fetchNodes();
+    return StorageDispatcher.INSTANCE.getNoSqlStore().getNodeStorage().getAllNodes();
+  }
+
+
+  public static Message updateNode(NodeInfoDescription desc) {
+    boolean successfullyUpdated = NodeClusterManager.INSTANCE.updateNodeInfoDescription(desc);
+    if (successfullyUpdated) {
+      StorageDispatcher.INSTANCE.getNoSqlStore().getNodeStorage().updateNode(desc);
+      return Notifications.success("Node modified");
+    }
+    return Notifications.error("Could not modify node");
+  }
+
+  public static void addNode(NodeInfoDescription desc) {
+
+    List<NodeInfoDescription> allNodes =
+            StorageDispatcher.INSTANCE.getNoSqlStore().getNodeStorage().getAllNodes();
+
+    boolean alreadyRegistered = allNodes.stream()
+            .anyMatch(n -> n.getNodeControllerId().equals(desc.getNodeControllerId()));
+
+    if (!alreadyRegistered) {
+      StorageDispatcher.INSTANCE.getNoSqlStore().getNodeStorage().storeNode(desc);
+    }
+  }
+
+  public static void deleteNode(String nodeControllerId) {
+    StorageDispatcher.INSTANCE.getNoSqlStore().getNodeStorage().deleteNode(nodeControllerId);
   }
 }
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INode.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INode.java
index 19c54d5..e5ae656 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INode.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INode.java
@@ -17,9 +17,19 @@
  */
 package org.apache.streampipes.rest.api;
 
+import org.apache.streampipes.model.node.NodeInfoDescription;
+
 import javax.ws.rs.core.Response;
 
 public interface INode {
 
+    Response addNode(String username, NodeInfoDescription desc);
+
+    Response updateNode(String username, String nodeControllerId, NodeInfoDescription desc);
+
+    Response deleteNode(String username, String nodeControllerId);
+
     Response getAvailableNodes();
+
+    Response getNodes();
 }
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/IPipeline.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/IPipeline.java
index d40afe6..9009b71 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/IPipeline.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/IPipeline.java
@@ -27,6 +27,8 @@ public interface IPipeline extends IPipelineElement {
 
   Response addPipeline(String username, Pipeline pipeline);
 
+  Response getOwn(String username);
+
   Response getSystemPipelines();
 
   Response start(String username, String pipelineId);
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Node.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Node.java
index fd1f3b8..b7f2dee 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Node.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Node.java
@@ -18,23 +18,67 @@
 package org.apache.streampipes.rest.impl;
 
 import org.apache.streampipes.manager.operations.Operations;
+import org.apache.streampipes.model.message.Notifications;
+import org.apache.streampipes.model.node.NodeInfoDescription;
 import org.apache.streampipes.rest.api.INode;
 import org.apache.streampipes.rest.shared.annotation.GsonClientModel;
+import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
+import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
 @Path("/v2/users/{username}/nodes")
 public class Node extends AbstractRestInterface implements INode {
+    private static final Logger LOG = LoggerFactory.getLogger(Node.class.getCanonicalName());
+
+    @POST
+    @JacksonSerialized
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Override
+    public Response addNode(@PathParam("username") String username, NodeInfoDescription desc) {
+        Operations.addNode(desc);
+        return statusMessage(Notifications.success("Node added"));
+    }
+
+
+    @PUT
+    @JacksonSerialized
+    @Path("/{nodeControllerId}")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Override
+    public Response updateNode(@PathParam("username") String username,
+                               @PathParam("nodeControllerId") String nodeControllerId, NodeInfoDescription desc) {
+        return statusMessage(Operations.updateNode(desc));
+    }
+
+    @DELETE
+    @Path("/{nodeControllerId}")
+    @Override
+    public Response deleteNode(@PathParam("username") String username,
+                               @PathParam("nodeControllerId") String nodeControllerId) {
+        Operations.deleteNode(nodeControllerId);
+        return statusMessage(Notifications.success("Node deleted"));
+    }
 
     @GET
-    @GsonClientModel
+    @Path("/available")
+    @JacksonSerialized
     @Produces(MediaType.APPLICATION_JSON)
     @Override
     public Response getAvailableNodes() {
+        // TODO: get from couchdb not from consul
         return ok(Operations.getAvailableNodes());
     }
+
+    @GET
+    @JacksonSerialized
+    @Produces(MediaType.APPLICATION_JSON)
+    @Override
+    public Response getNodes() {
+        return ok(Operations.getAllNodes());
+    }
 }
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineWithUserResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineWithUserResource.java
index b7316a9..2cf26cd 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineWithUserResource.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineWithUserResource.java
@@ -244,6 +244,7 @@ public class PipelineWithUserResource extends AbstractRestInterface implements I
         storedPipeline.setActions(pipeline.getActions());
         storedPipeline.setCreatedAt(System.currentTimeMillis());
         storedPipeline.setPipelineCategories(pipeline.getPipelineCategories());
+        storedPipeline.setEventRelayStrategy(pipeline.getEventRelayStrategy());
         Operations.updatePipeline(storedPipeline);
         return statusMessage(Notifications.success("Pipeline modified"));
     }
@@ -263,6 +264,7 @@ public class PipelineWithUserResource extends AbstractRestInterface implements I
 //        storedPipeline.setActions(pipeline.getActions());
 //        storedPipeline.setCreatedAt(System.currentTimeMillis());
 //        storedPipeline.setPipelineCategories(pipeline.getPipelineCategories());
+//        storedPipeline.setEventRelayStrategy(pipeline.getEventRelayStrategy());
 //        Operations.updatePipeline(storedPipeline);
         return statusMessage(Notifications.success("Pipeline processors migrated"));
     }
diff --git a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/builder/AbstractProcessingElementBuilder.java b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/builder/AbstractProcessingElementBuilder.java
index 8b12102..637b2c4 100644
--- a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/builder/AbstractProcessingElementBuilder.java
+++ b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/builder/AbstractProcessingElementBuilder.java
@@ -93,6 +93,10 @@ public abstract class AbstractProcessingElementBuilder<BU extends
     return me();
   }
 
+  public BU requiredResource() {
+    return me();
+  }
+
   private List<MappingProperty> rewrite(List<MappingProperty> mappingProperties, int index) {
     mappingProperties.forEach(mp -> mp.setRequirementSelector
             (getIndex(index) + PropertySelectorConstants.PROPERTY_DELIMITER + mp
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INode.java b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/helpers/CollectedResourceRequirements.java
similarity index 85%
copy from streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INode.java
copy to streampipes-sdk/src/main/java/org/apache/streampipes/sdk/helpers/CollectedResourceRequirements.java
index 19c54d5..20a8786 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INode.java
+++ b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/helpers/CollectedResourceRequirements.java
@@ -15,11 +15,7 @@
  * limitations under the License.
  *
  */
-package org.apache.streampipes.rest.api;
+package org.apache.streampipes.sdk.helpers;
 
-import javax.ws.rs.core.Response;
-
-public interface INode {
-
-    Response getAvailableNodes();
+public class CollectedResourceRequirements {
 }
diff --git a/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/json/GsonSerializer.java b/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/json/GsonSerializer.java
index 45114a9..357a5e4 100644
--- a/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/json/GsonSerializer.java
+++ b/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/json/GsonSerializer.java
@@ -35,6 +35,16 @@ import org.apache.streampipes.model.connect.rules.stream.RemoveDuplicatesTransfo
 import org.apache.streampipes.model.connect.rules.TransformationRuleDescription;
 import org.apache.streampipes.model.grounding.TopicDefinition;
 import org.apache.streampipes.model.grounding.TransportProtocol;
+import org.apache.streampipes.model.node.NodeBrokerDescription;
+import org.apache.streampipes.model.node.NodeInfoDescription;
+import org.apache.streampipes.model.node.container.DeploymentContainer;
+import org.apache.streampipes.model.node.container.DockerContainer;
+import org.apache.streampipes.model.node.meta.GeoLocation;
+import org.apache.streampipes.model.node.meta.StaticNodeMetadata;
+import org.apache.streampipes.model.node.resources.NodeResource;
+import org.apache.streampipes.model.node.resources.software.ContainerRuntime;
+import org.apache.streampipes.model.node.resources.software.DockerContainerRuntime;
+import org.apache.streampipes.model.node.resources.software.NvidiaContainerRuntime;
 import org.apache.streampipes.model.output.OutputStrategy;
 import org.apache.streampipes.model.quality.EventPropertyQualityDefinition;
 import org.apache.streampipes.model.quality.EventStreamQualityDefinition;
@@ -106,6 +116,9 @@ public class GsonSerializer {
             .registerSubtype(GenericAdapterSetDescription.class, "org.apache.streampipes.model.connect.adapter.GenericAdapterSetDescription")
             .registerSubtype(GenericAdapterStreamDescription.class, "org.apache.streampipes.model.connect.adapter.GenericAdapterStreamDescription"));
 
+    builder.registerTypeAdapter(ContainerRuntime.class, new JsonLdSerializer<ContainerRuntime>());
+    builder.registerTypeAdapter(DeploymentContainer.class, new JsonLdSerializer<DeploymentContainer>());
+
     builder.setPrettyPrinting();
     return builder;
   }
diff --git a/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/jsonld/CustomAnnotationProvider.java b/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/jsonld/CustomAnnotationProvider.java
index 7d0c404..4a3cf13 100644
--- a/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/jsonld/CustomAnnotationProvider.java
+++ b/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/jsonld/CustomAnnotationProvider.java
@@ -44,6 +44,17 @@ import org.apache.streampipes.model.datalake.DataLakeMeasure;
 import org.apache.streampipes.model.graph.*;
 import org.apache.streampipes.model.grounding.*;
 import org.apache.streampipes.model.monitoring.ElementStatusInfoSettings;
+import org.apache.streampipes.model.node.NodeBrokerDescription;
+import org.apache.streampipes.model.node.NodeInfoDescription;
+import org.apache.streampipes.model.node.container.DockerContainer;
+import org.apache.streampipes.model.node.meta.GeoLocation;
+import org.apache.streampipes.model.node.meta.StaticNodeMetadata;
+import org.apache.streampipes.model.node.resources.NodeResource;
+import org.apache.streampipes.model.node.resources.fielddevice.FieldDeviceAccessResource;
+import org.apache.streampipes.model.node.resources.hardware.*;
+import org.apache.streampipes.model.node.resources.software.DockerContainerRuntime;
+import org.apache.streampipes.model.node.resources.software.NvidiaContainerRuntime;
+import org.apache.streampipes.model.node.resources.software.SoftwareResource;
 import org.apache.streampipes.model.output.*;
 import org.apache.streampipes.model.quality.*;
 import org.apache.streampipes.model.runtime.RuntimeOptionsRequest;
@@ -185,7 +196,22 @@ public class CustomAnnotationProvider implements EmpireAnnotationProvider {
             StreamPipesJsonLdContainer.class,
             DataLakeMeasure.class,
             SpDataStreamRelay.class,
-            SpDataStreamRelayContainer.class
+            SpDataStreamRelayContainer.class,
+            NodeInfoDescription.class,
+            NodeResource.class,
+            DockerContainerRuntime.class,
+            NvidiaContainerRuntime.class,
+            DockerContainer.class,
+            StaticNodeMetadata.class,
+            NodeBrokerDescription.class,
+            GeoLocation.class,
+            HardwareResource.class,
+            CPU.class,
+            MEM.class,
+            DISK.class,
+            GPU.class,
+            SoftwareResource.class,
+            FieldDeviceAccessResource.class
     );
   }
 }
diff --git a/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/jsonld/JsonLdTransformer.java b/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/jsonld/JsonLdTransformer.java
index 6a6d059..55b4781 100644
--- a/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/jsonld/JsonLdTransformer.java
+++ b/streampipes-serializers/src/main/java/org/apache/streampipes/serializers/jsonld/JsonLdTransformer.java
@@ -70,7 +70,10 @@ public class JsonLdTransformer implements RdfTransformer {
           StreamPipes.DASHBOARD_WIDGET_MODEL,
           StreamPipes.DASHBOARD_MODEL,
           StreamPipes.DATA_EXPLORER_WIDGET_MODEL,
-          StreamPipes.DATA_STREAM_RELAY_CONTAINER
+          StreamPipes.DATA_STREAM_RELAY_CONTAINER,
+          StreamPipes.NODE_INFO_DESCRIPTION,
+          StreamPipes.DEPLOYMENT_CONTAINER,
+          StreamPipes.CONTAINER_RUNTIME
   );
 
   private List<String> selectedRootElements;
diff --git a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java
index 3f69160..a42f82c 100644
--- a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java
+++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java
@@ -55,4 +55,5 @@ public interface INoSqlStorage {
 
   IVisualizablePipelineStorage getVisualizablePipelineStorage();
 
+  INodeInfoStorage getNodeStorage();
 }
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INode.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INodeInfoStorage.java
similarity index 61%
copy from streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INode.java
copy to streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INodeInfoStorage.java
index 19c54d5..cc1cb92 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INode.java
+++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INodeInfoStorage.java
@@ -15,11 +15,25 @@
  * limitations under the License.
  *
  */
-package org.apache.streampipes.rest.api;
 
-import javax.ws.rs.core.Response;
+package org.apache.streampipes.storage.api;
 
-public interface INode {
+import org.apache.streampipes.model.node.NodeInfoDescription;
 
-    Response getAvailableNodes();
+import java.util.List;
+import java.util.Optional;
+
+public interface INodeInfoStorage {
+
+    List<NodeInfoDescription> getAllNodes();
+
+    List<NodeInfoDescription> getAllActiveNodes();
+
+    void storeNode(NodeInfoDescription desc);
+
+    void updateNode(NodeInfoDescription desc);
+
+    Optional<NodeInfoDescription> getNode(String nodeControllerId);
+
+    void deleteNode(String nodeControllerId);
 }
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java
index f703874..a88c8db 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java
@@ -110,5 +110,10 @@ public enum CouchDbStorageManager implements INoSqlStorage {
     return new VisualizablePipelineStorageImpl();
   }
 
+  @Override
+  public INodeInfoStorage getNodeStorage() {
+    return new NodeInfoStorageImpl();
+  }
+
 
 }
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/NodeInfoStorageImpl.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/NodeInfoStorageImpl.java
new file mode 100644
index 0000000..6a8a7e4
--- /dev/null
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/NodeInfoStorageImpl.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.storage.couchdb.impl;
+
+import org.apache.streampipes.model.node.NodeInfoDescription;
+import org.apache.streampipes.storage.api.INodeInfoStorage;
+import org.apache.streampipes.storage.couchdb.dao.AbstractDao;
+import org.apache.streampipes.storage.couchdb.utils.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class NodeInfoStorageImpl extends AbstractDao<NodeInfoDescription> implements INodeInfoStorage {
+    private static final Logger LOG = LoggerFactory.getLogger(NodeInfoStorageImpl.class.getCanonicalName());
+
+    public NodeInfoStorageImpl() {
+        super(Utils::getCouchDbNodeClient, NodeInfoDescription.class);
+    }
+
+    @Override
+    public List<NodeInfoDescription> getAllNodes() {
+        return findAll();
+    }
+
+    @Override
+    public List<NodeInfoDescription> getAllActiveNodes() {
+        List<NodeInfoDescription> allNodes = getAllNodes();
+        return allNodes.stream()
+                .filter(NodeInfoDescription::isActive)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public void storeNode(NodeInfoDescription desc) {
+        LOG.info("Store new node description with node id={}, url={}", desc.getNodeControllerId(),
+                desc.getHostname() + ":" + desc.getPort());
+        persist(desc);
+    }
+
+    @Override
+    public void updateNode(NodeInfoDescription desc) {
+        LOG.info("Update node description for node id={}, url={}", desc.getNodeControllerId(),
+                desc.getHostname() + ":" + desc.getPort());
+
+        Optional<NodeInfoDescription> storedNode = getNode(desc.getNodeControllerId());
+
+        if (storedNode.isPresent()) {
+            desc.setId(storedNode.get().getId());
+            desc.setRev(storedNode.get().getRev());
+
+            update(desc);
+        }
+    }
+
+    @Override
+    public Optional<NodeInfoDescription> getNode(String nodeControllerId) {
+        return findAll().stream()
+                .filter(n -> n.getNodeControllerId().equals(nodeControllerId))
+                .findAny();
+    }
+
+    @Override
+    public void deleteNode(String nodeControllerId) {
+        LOG.info("Delete node with node id={}", nodeControllerId);
+        Optional<NodeInfoDescription> storedNode = getNode(nodeControllerId);
+
+        storedNode.ifPresent(nodeInfoDescription -> delete(nodeInfoDescription.getId()));
+    }
+}
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
index f3ea384..defa24c 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
@@ -132,6 +132,12 @@ public class Utils {
     return dbClient;
   }
 
+  public static CouchDbClient getCouchDbNodeClient() {
+    CouchDbClient dbClient = new CouchDbClient(props("nodes"));
+    dbClient.setGsonBuilder(GsonSerializer.getGsonBuilder());
+    return dbClient;
+  }
+
   public static CouchDbClient getCouchDbInternalUsersClient() {
     CouchDbClient dbClient = new CouchDbClient(props("_users"));
     return dbClient;
diff --git a/ui/package.json b/ui/package.json
index 054ee03..31a9149 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -40,7 +40,7 @@
     "angular-datatables": "9.0.2",
     "angular-gridster2": "8.3.0",
     "angular-loading-bar": "0.8.0",
-    "angular-material-icons": "0.4.0",
+    "angular-material-icons": "^0.4.0",
     "angular-plotly.js": "1.5.0",
     "angular-tree-component": "8.5.6",
     "angular-ui-tree": "2.9.0",
@@ -63,7 +63,7 @@
     "konva": "3.2.4",
     "leaflet": "1.6.0",
     "lodash": "4.17.20",
-    "material-design-icons": "3.0.1",
+    "material-design-icons": "^3.0.1",
     "ng-file-upload": "9.0.13",
     "ng-pick-datetime": "7.0.0",
     "ng-prettyjson": "0.1.8",
diff --git a/ui/src/app/app-asset-monitoring/components/dashboard-overview/dashboard-overview.component.css b/ui/src/app/app-asset-monitoring/components/dashboard-overview/dashboard-overview.component.css
index 213288b..149a042 100644
--- a/ui/src/app/app-asset-monitoring/components/dashboard-overview/dashboard-overview.component.css
+++ b/ui/src/app/app-asset-monitoring/components/dashboard-overview/dashboard-overview.component.css
@@ -16,7 +16,7 @@
  *
  */
 
-.example-card {
+.node-card {
     max-width: 400px;
 }
 
diff --git a/ui/src/app/app-container/app-container.component.html b/ui/src/app/app-container/app-container.component.html
index ee39e96..e9737f4 100644
--- a/ui/src/app/app-container/app-container.component.html
+++ b/ui/src/app/app-container/app-container.component.html
@@ -31,7 +31,7 @@
     <div class="fixed-height page-container-padding-inner" fxLayout="column" fxFlex="100" *ngIf="!isAppActive">
         <div fxFlex="100" fxLayout="column">
             <h1>My apps</h1>
-            <mat-card class="example-card" *ngFor="let installedApp of installedApps">
+            <mat-card class="node-card" *ngFor="let installedApp of installedApps">
                 <div fxFlex="100" fxLayout="row">
                     <div fxFlex="75" fxLayout="column" fxLayoutAlign="center start">
                         <mat-card-header>
diff --git a/ui/src/app/app-overview/app-overview.component.html b/ui/src/app/app-overview/app-overview.component.html
index ce1010e..eb5ff2f 100644
--- a/ui/src/app/app-overview/app-overview.component.html
+++ b/ui/src/app/app-overview/app-overview.component.html
@@ -31,7 +31,7 @@
     <div class="fixed-height page-container-padding-inner" fxLayout="column" fxFlex="100" *ngIf="!appOpen">
         <div fxFlex="100" fxLayout="column">
             <h1>My apps</h1>
-            <mat-card class="example-card" *ngFor="let app of apps">
+            <mat-card class="node-card" *ngFor="let app of apps">
                 <div fxFlex="100" fxLayout="row">
                     <div fxFlex="75" fxLayout="column" fxLayoutAlign="center start">
                         <mat-card-header>
diff --git a/ui/src/app/configuration/configuration.component.html b/ui/src/app/configuration/configuration.component.html
index 39116ef..2d2e3d9 100644
--- a/ui/src/app/configuration/configuration.component.html
+++ b/ui/src/app/configuration/configuration.component.html
@@ -38,6 +38,6 @@
         <sp-datalake-configuration fxFlex="100"></sp-datalake-configuration>
     </div>
     <div class="page-container-padding-inner" fxLayout="column" fxFlex="100" *ngIf="selectedIndex == 3">
-        <edge-configuration fxFlex="100"></edge-configuration>
+        <node-configuration fxFlex="100"></node-configuration>
     </div>
 </div>
diff --git a/ui/src/app/configuration/configuration.module.ts b/ui/src/app/configuration/configuration.module.ts
index 8ab9992..373fd05 100644
--- a/ui/src/app/configuration/configuration.module.ts
+++ b/ui/src/app/configuration/configuration.module.ts
@@ -41,7 +41,9 @@ import { MessagingConfigurationComponent } from './messaging-configuration/messa
 import { DragDropModule } from '@angular/cdk/drag-drop';
 import { DatalakeConfigurationComponent } from './datalake-configuration/datalake-configuration.component';
 import { DatalakeRestService } from '../core-services/datalake/datalake-rest.service';
-import { EdgeConfigurationComponent } from "./edge-configuration/edge-configuration.component";
+import { NodeConfigurationComponent } from "./node-configuration/node-configuration.component";
+import { NodeConfigurationDetailsComponent } from './node-configuration/node-configuration-details/node-configuration-details.component';
+import {MatChipsModule} from "@angular/material/chips";
 
 @NgModule({
     imports: [
@@ -55,7 +57,8 @@ import { EdgeConfigurationComponent } from "./edge-configuration/edge-configurat
         MatCheckboxModule,
         MatTooltipModule,
         FormsModule,
-        DragDropModule
+        DragDropModule,
+        MatChipsModule
     ],
     declarations: [
         ConfigurationComponent,
@@ -67,9 +70,10 @@ import { EdgeConfigurationComponent } from "./edge-configuration/edge-configurat
         ConsulConfigsNumberComponent,
         PipelineElementConfigurationComponent,
         MessagingConfigurationComponent,
-        EdgeConfigurationComponent,
+        NodeConfigurationComponent,
         MessagingConfigurationComponent,
-        DatalakeConfigurationComponent
+        DatalakeConfigurationComponent,
+        NodeConfigurationDetailsComponent
     ],
     providers: [
       ConfigurationService,
diff --git a/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.html b/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.html
index ba8f63e..718dfa7 100644
--- a/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.html
+++ b/ui/src/app/configuration/datalake-configuration/datalake-configuration.component.html
@@ -20,7 +20,7 @@
     <div fxFlex="100" fxLayout="row wrap" fxLayoutAlign="start stretch">
         <div fxFlex="100" class="assemblyOptions sp-blue-bg" style="padding:5px;">
             <div fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
-                <h3>Datalake Settings</h3>
+                <h4>Datalake Settings</h4>
                 <span flex></span>
             </div>
         </div>
diff --git a/ui/src/app/configuration/edge-configuration/edge-configuration.component.html b/ui/src/app/configuration/edge-configuration/edge-configuration.component.html
deleted file mode 100644
index e7356cc..0000000
--- a/ui/src/app/configuration/edge-configuration/edge-configuration.component.html
+++ /dev/null
@@ -1,77 +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 fxFlex="100" fxLayout="column" style="margin-top:40px;">
-    <div fxFlex="100" fxLayout="row wrap" fxLayoutAlign="start stretch">
-        <div fxFlex="100" class="assemblyOptions sp-blue-bg" style="padding:5px;">
-            <div fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
-                <h4>Node Overview</h4>
-                <span flex></span>
-            </div>
-        </div>
-        <div fxFlex="100" fxLayoutAlign="start start" class="sp-blue-border page-container-padding-inner">
-            <mat-grid-list
-                    [cols]="3" [rowHeight]="300"
-                    [gutterSize]="10" fxFlex="100">
-
-                <mat-grid-tile class="gray" *ngFor="let node of availableEdgeNodes">
-                    <mat-card class="example-card">
-                        <mat-card-header>
-                            <div mat-card-avatar class="node-header-avatar"><i class="fas fa-server fa-3x"></i></div>
-                            <mat-card-title>{{node.nodeControllerId}}</mat-card-title>
-                            <mat-card-subtitle>{{node.hostname}} |
-                                <b>{{node.nodeResources.softwareResource.os}}</b></mat-card-subtitle>
-                        </mat-card-header>
-                        <mat-card-content>
-                            <div class="div-node-tag" *ngFor="let tag of node.staticNodeMedata.locationTags">
-                                <span class="span-node-tag">{{tag}}</span>&nbsp;
-                            </div>
-                            <div class="div-sa-tag" *ngFor="let device of node.nodeResources.fieldDeviceAccessResourceList">
-                                <span class="span-sa-tag">{{device.deviceName}}</span>&nbsp;
-                            </div>
-                            <hr/>
-                            <div>
-                                <ul class="ft-foot">
-                                    <li><span class="inclusion" style="color: darkgray"><i
-                                            class="fas fa-microchip"></i></span><b>{{node.nodeResources.hardwareResource.cpu.cores}}</b> cores
-                                    </li>
-                                    <li><span class="inclusion"
-                                              style="color: darkgray"><i class="fas fa-memory"></i></span><b>{{node.nodeResources.hardwareResource.memory.memTotal}}</b> GiB</li>
-                                    <li><span class="inclusion"
-                                              style="color: darkgray"><i class="fas fa-hdd"></i></span><b>{{node.nodeResources.hardwareResource.disk.diskTotal}}</b>
-                                        GiB</li>
-                                </ul>
-                                <ul class="ft-foot">
-                                    <li><span class="inclusion"
-                                              style="color: darkgray"><i class="fas fa-cube"></i></span>{{node.nodeResources.hardwareResource.cpu.arch}}</li>
-                                    <li><span class="inclusion"
-                                              style="color: darkgray"><i class="fab fa-docker"></i></span>{{node.nodeResources.softwareResource.containerRuntime.serverVersion}}</li>
-                                    <li><span class="inclusion"
-                                              style="color: darkgray"><i class="fas fa-meteor"></i></span>GPU
-                                        <b>{{nvidiaRuntime(node)}}</b></li>
-                                </ul>
-                            </div>
-                        </mat-card-content>
-                        <mat-card-actions>
-                        </mat-card-actions>
-                    </mat-card>
-                </mat-grid-tile>
-            </mat-grid-list>
-        </div>
-    </div>
-</div>
\ No newline at end of file
diff --git a/ui/src/app/configuration/edge-configuration/edge-configuration.component.ts b/ui/src/app/configuration/edge-configuration/edge-configuration.component.ts
deleted file mode 100644
index c13c38f..0000000
--- a/ui/src/app/configuration/edge-configuration/edge-configuration.component.ts
+++ /dev/null
@@ -1,92 +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} from "@angular/core";
-import {ConfigurationService} from "../shared/configuration.service";
-import {
-    FieldDeviceAccessResource,
-    NodeInfoDescription, NvidiaContainerRuntime
-} from "../../core-model/gen/streampipes-model";
-
-@Component({
-    selector: 'edge-configuration',
-    templateUrl: './edge-configuration.component.html',
-    styleUrls: ['./edge-configuration.component.css']
-})
-export class EdgeConfigurationComponent {
-
-    loadingCompleted: boolean = false;
-    availableEdgeNodes: NodeInfoDescription[];
-
-    os: String;
-    serverVersion: String;
-    availableGPU: boolean = false;
-    cpuCores: number;
-    cpuArch: string;
-    memTotal: number;
-    diskTotal: number;
-    gpuCores: number;
-    gpuType: string;
-    locationTags: String[];
-    fieldDevices: FieldDeviceAccessResource[];
-
-    constructor(private configurationService: ConfigurationService) {
-
-    }
-
-    ngOnInit() {
-        this.getAvailableEdgeNodes();
-    }
-
-    getAvailableEdgeNodes() {
-        this.configurationService.getAvailableEdgeNodes().subscribe(response => {
-            this.availableEdgeNodes = response;
-            this.availableEdgeNodes.forEach(x => {
-                // Raspbian is too long -> shorten it
-                let os = x.nodeResources.softwareResource.os;
-                if (os.includes('Raspbian')) {
-                    if (os.includes('buster')) {
-                        if (os.includes('10')) {
-                            x.nodeResources.softwareResource.os = 'Raspbian 10 (buster)'
-                        }
-                    }
-                }
-                let memTotal = x.nodeResources.hardwareResource.memory.memTotal;
-                let diskTotal = x.nodeResources.hardwareResource.disk.diskTotal;
-                x.nodeResources.hardwareResource.memory.memTotal = this.bytesToGB(memTotal);
-                x.nodeResources.hardwareResource.disk.diskTotal = this.bytesToGB(diskTotal)
-            });
-        })
-    }
-
-    bytesToGB(bytes) {
-        var b = 1;
-        var kb = b * 1024;
-        var mb = kb * 1024;
-        var gb = mb * 1024;
-        return (Math.round((bytes / gb) * 100) / 100);
-    };
-
-    nvidiaRuntime(node: NodeInfoDescription) {
-        let nvidiaRuntime = false;
-        if (node.nodeResources.softwareResource.containerRuntime instanceof NvidiaContainerRuntime) {
-            nvidiaRuntime = true;
-        }
-        return nvidiaRuntime;
-    }
-}
\ No newline at end of file
diff --git a/ui/src/app/configuration/node-configuration/node-configuration-details/node-configuration-details.component.html b/ui/src/app/configuration/node-configuration/node-configuration-details/node-configuration-details.component.html
new file mode 100644
index 0000000..6712a6e
--- /dev/null
+++ b/ui/src/app/configuration/node-configuration/node-configuration-details/node-configuration-details.component.html
@@ -0,0 +1,61 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<!--<div class="sp-dialog-container">-->
+<!--    <div class="sp-dialog-content padding-20">-->
+<!--        <div fxFlex="100" fxLayout="column">-->
+<!--            <p>node-settings works!</p>-->
+<!--        </div>-->
+<!--    </div>-->
+<!--</div>-->
+<div class="sp-dialog-container">
+    <div class="sp-dialog-content padding-20">
+        <div fxFlex="100" fxLayout="column">
+            <div fxFlex="100" fxLayout="column">
+                {{node.nodeControllerId}}
+
+                <mat-form-field class="example-chip-list">
+                    <mat-label>Node location tag</mat-label>
+                    <mat-chip-list #chipList aria-label="Node location tag">
+                        <mat-chip *ngFor="let tag of tmpTags" [selectable]="selectable"
+                                  [removable]="removable" (removed)="remove(tag)">
+                            {{tag}}
+                            <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
+                        </mat-chip>
+                        <input placeholder="New tag..."
+                               [matChipInputFor]="chipList"
+                               [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
+                               [matChipInputAddOnBlur]="addOnBlur"
+                               (matChipInputTokenEnd)="add($event)">
+                    </mat-chip-list>
+                </mat-form-field>
+
+            </div>
+        </div>
+    </div>
+    <mat-divider></mat-divider>
+    <div class="sp-dialog-actions" fxLayoutAlign="left center">
+        <button [disabled]="saving || saved" mat-button mat-raised-button
+                color="primary" (click)="updateNodeInfo()" style="margin-right:10px;">
+            Save
+        </button>
+        <button mat-button mat-raised-button class="mat-basic" (click)="hide()">
+            {{saved ? 'Close' : 'Cancel'}}
+        </button>
+    </div>
+</div>
diff --git a/ui/src/app/configuration/node-configuration/node-configuration-details/node-configuration-details.component.scss b/ui/src/app/configuration/node-configuration/node-configuration-details/node-configuration-details.component.scss
new file mode 100644
index 0000000..fe91666
--- /dev/null
+++ b/ui/src/app/configuration/node-configuration/node-configuration-details/node-configuration-details.component.scss
@@ -0,0 +1,98 @@
+/*
+ * 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 '../../../../scss/sp/sp-dialog.scss';
+
+.sp-dialog-container {
+  width: 520px;
+}
+
+.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;
+}
+
+.status-text {
+  font-size: 14pt;
+  margin-top:10px;
+}
+
+.status-subtext {
+  font-size: 12pt;
+}
+
+mat-slider {
+  width: 300px;
+}
+
+.execution-policy-radio-group {
+  display: flex;
+  flex-direction: column;
+  //margin: 15px 0;
+}
+
+.execution-policy-radio-button {
+  margin: 5px;
+}
+
+
+.execution-policy-action-buttons {
+  padding-bottom: 20px;
+}
+
+.example-headers-align .mat-expansion-panel-header-title,
+.example-headers-align .mat-expansion-panel-header-description {
+  flex-basis: 0;
+}
+
+.example-headers-align .mat-expansion-panel-header-description {
+  justify-content: space-between;
+  align-items: center;
+}
+
+.example-headers-align .mat-form-field + .mat-form-field {
+  margin-left: 8px;
+}
+
+.mat-form-field[dense] {
+  .mat-form-field-flex > .mat-form-field-infix {
+    padding: 0.4em 0px !important;
+  }
+  .mat-form-field-label-wrapper {
+    top: -1.5em;
+  }
+  &.mat-form-field-appearance-outline.mat-form-field-can-float.mat-form-field-should-float .mat-form-field-label {
+    transform: translateY(-1.1em) scale(.75);
+  }
+  .mat-form-field-wrapper{
+    padding-bottom: 0;
+  }
+}
\ No newline at end of file
diff --git a/ui/src/app/configuration/node-configuration/node-configuration-details/node-configuration-details.component.ts b/ui/src/app/configuration/node-configuration/node-configuration-details/node-configuration-details.component.ts
new file mode 100644
index 0000000..ff2579d
--- /dev/null
+++ b/ui/src/app/configuration/node-configuration/node-configuration-details/node-configuration-details.component.ts
@@ -0,0 +1,90 @@
+/*
+ * 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, Input, OnInit} from '@angular/core';
+import {NodeInfoDescription} from "../../../core-model/gen/streampipes-model";
+import {FormGroup} from "@angular/forms";
+import {DialogRef} from "../../../core-ui/dialog/base-dialog/dialog-ref";
+import {MatChipInputEvent} from "@angular/material/chips";
+import {COMMA, ENTER} from "@angular/cdk/keycodes";
+
+export interface Fruit {
+  name: string;
+}
+
+@Component({
+  selector: 'node-configuration-details',
+  templateUrl: './node-configuration-details.component.html',
+  styleUrls: ['./node-configuration-details.component.scss']
+})
+export class NodeConfigurationDetailsComponent implements OnInit {
+
+  submitNodeForm: FormGroup = new FormGroup({});
+  saving: boolean = false;
+  saved: boolean = false;
+  advancedSettings: boolean;
+
+  visible = true;
+  selectable = true;
+  removable = true;
+  addOnBlur = true;
+  readonly separatorKeysCodes: number[] = [ENTER, COMMA];
+  tmpTags: string[];
+
+  @Input()
+  node: NodeInfoDescription;
+
+  constructor(private dialogRef: DialogRef<NodeConfigurationDetailsComponent>) {
+  }
+
+  ngOnInit(): void {
+    this.advancedSettings = false;
+    this.tmpTags = this.node.staticNodeMetadata.locationTags.map(x => x);
+  }
+
+  updateNodeInfo() {
+
+  }
+
+  hide() {
+    this.dialogRef.close();
+  }
+
+  add(event: MatChipInputEvent): void {
+    const input = event.input;
+    const value = event.value;
+
+    if ((value || '').trim()) {
+      this.tmpTags.push(value.trim());
+    }
+
+    // Reset the input value
+    if (input) {
+      input.value = '';
+    }
+  }
+
+  remove(tag: string): void {
+    const index = this.tmpTags.indexOf(tag);
+
+    if (index >= 0) {
+      this.tmpTags.splice(index, 1);
+    }
+  }
+
+}
diff --git a/ui/src/app/configuration/node-configuration/node-configuration.component.html b/ui/src/app/configuration/node-configuration/node-configuration.component.html
new file mode 100644
index 0000000..4a98d19
--- /dev/null
+++ b/ui/src/app/configuration/node-configuration/node-configuration.component.html
@@ -0,0 +1,130 @@
+<!--
+  ~ 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 fxFlex="100" fxLayout="column" style="margin-top:40px;">
+    <div fxFlex="100" fxLayout="row wrap" fxLayoutAlign="start stretch">
+        <div fxFlex="100" class="assemblyOptions sp-blue-bg" style="padding:5px;">
+            <div fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
+                <h4>Node Overview</h4>
+                <span flex></span>
+            </div>
+        </div>
+        <div fxFlex="100" fxLayoutAlign="start start" class="sp-blue-border page-container-padding-inner">
+            <mat-grid-list
+                    [cols]="3" [rowHeight]="300"
+                    [gutterSize]="10" fxFlex="100">
+
+                <mat-grid-tile class="gray" *ngFor="let node of nodes">
+                    <mat-card class="node-card">
+                        <mat-card-header class="header">
+                            <div class="div-icon-node-actions">
+                                <button mat-icon-button class="node-mat-icon-button"
+                                        [disabled]="node.active"
+                                        (click)="changeNodeState(node, true)">
+                                    <mat-icon
+                                            [ngClass]="node.active ? 'node-activated' : 'node-inactive'"
+                                            matTooltip="{{node.active ? 'node alive and running' : 'activate node'}}"
+                                            matTooltipPosition="above">
+                                        play_circle_filled
+                                    </mat-icon>
+                                </button>
+                                <button mat-icon-button class="node-mat-icon-button"
+                                        [disabled]="!node.active"
+                                        (click)="changeNodeState(node, false)">
+                                    <mat-icon
+                                            [ngClass]="node.active ? 'node-inactive' : 'node-deactivated'"
+                                            matTooltip="{{!node.active ? 'node deactivated' : 'deactivate node'}}"
+                                            matTooltipPosition="above">
+                                        pause_circle_filled
+                                    </mat-icon>
+                                </button>
+                                <button mat-icon-button class="node-mat-icon-button"
+                                        [disabled]="node.active"
+                                        (click)="evict(node)">
+                                    <mat-icon
+                                            [ngClass]="node.active ? 'node-inactive' : 'node-evict'"
+                                            matTooltip="{{!node.active ? 'evict processors' :
+                                            'evict processors option not available'}}"
+                                            matTooltipPosition="above">
+                                        swap_horizontal_circle_filled
+                                    </mat-icon>
+                                </button>
+                                <button mat-icon-button class="node-mat-icon-button"
+                                        matTooltip="Edit node configuration"
+                                        matTooltipPosition="above"
+                                        (click)="settings(node)">
+                                    <mat-icon
+                                            [ngClass]="'node-inactive'">
+                                        info
+                                    </mat-icon>
+                                </button>
+                            </div>
+<!--                            <div mat-card-avatar class="node-header-avatar"><i class="fas fa-server fa-2x"></i></div>-->
+                            <div mat-card-avatar class="node-header-avatar">
+                                <button mat-icon-button class="node-mat-icon-button" disabled>
+                                    <mat-icon [ngClass]="'node-inactive'">
+                                        storage
+                                    </mat-icon>
+                                </button>
+                            </div>
+                            <mat-card-title
+                                    style="font-size: 12pt">{{node.nodeControllerId}}</mat-card-title>
+                            <mat-card-subtitle style="font-size: 10pt">{{node.hostname}} |
+                                <b>{{node.nodeResources.softwareResource.os}}</b></mat-card-subtitle>
+                        </mat-card-header>
+                        <mat-card-content>
+                            <div class="div-node-tag" *ngFor="let tag of node.staticNodeMetadata.locationTags">
+                                <span class="span-node-tag">{{tag}}</span>&nbsp;
+                            </div>
+                            <div class="div-sa-tag" *ngFor="let device of node.nodeResources.fieldDeviceAccessResourceList">
+                                <span class="span-sa-tag">{{device.deviceName}}</span>&nbsp;
+                            </div>
+                            <hr/>
+                            <div>
+                                <ul class="ft-foot">
+                                    <li><span class="inclusion" style="color: darkgray"><i
+                                            class="fas fa-microchip"></i></span><b>{{node.nodeResources.hardwareResource.cpu.cores}}</b> cores
+                                    </li>
+                                    <li><span class="inclusion"
+                                              style="color: darkgray"><i class="fas fa-memory"></i></span><b>{{this.bytesToGB(node.nodeResources.hardwareResource.memory.memTotal)}}</b> GiB</li>
+                                    <li><span class="inclusion"
+                                              style="color: darkgray"><i class="fas fa-hdd"></i></span><b>{{this.bytesToGB(node.nodeResources.hardwareResource.disk.diskTotal)}}</b>
+                                        GiB</li>
+                                </ul>
+                                <ul class="ft-foot">
+                                    <li><span class="inclusion"
+                                              style="color: darkgray"><i class="fas fa-cube"></i></span>{{node.nodeResources.hardwareResource.cpu.arch}}</li>
+                                    <li><span class="inclusion"
+                                              style="color: darkgray"><i class="fab fa-docker"></i></span>{{node.nodeResources.softwareResource.containerRuntime.serverVersion}}</li>
+                                    <li><span class="inclusion"
+                                              style="color: darkgray"><i class="fas fa-meteor"></i></span>GPU
+                                        <b>{{nvidiaRuntime(node)}}</b></li>
+                                </ul>
+                            </div>
+                            <div *ngFor="let container of node.registeredContainers">
+                                <span>{{container.containerName}}</span>
+                            </div>
+                        </mat-card-content>
+                        <mat-card-actions>
+                        </mat-card-actions>
+                    </mat-card>
+                </mat-grid-tile>
+            </mat-grid-list>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/configuration/edge-configuration/edge-configuration.component.css b/ui/src/app/configuration/node-configuration/node-configuration.component.scss
similarity index 70%
rename from ui/src/app/configuration/edge-configuration/edge-configuration.component.css
rename to ui/src/app/configuration/node-configuration/node-configuration.component.scss
index 19649bb..4e4fe65 100644
--- a/ui/src/app/configuration/edge-configuration/edge-configuration.component.css
+++ b/ui/src/app/configuration/node-configuration/node-configuration.component.scss
@@ -16,6 +16,9 @@
  *
  */
 
+@import 'src/scss/sp/colors';
+@import url("https://fonts.googleapis.com/icon?family=Material+Icons");
+
 .page-container-padding-inner {
     padding: 10px;
 }
@@ -24,8 +27,8 @@
     width:80%;
 }
 
-.example-card {
-    max-width: 400px;
+.node-card {
+    max-width: 450px;
 }
 
 mat-card {
@@ -33,6 +36,21 @@ mat-card {
     margin: 10px;
 }
 
+.mat-card-header-text {
+    margin: 0 !important;
+}
+
+.node-mat-icon-button {
+    width: 25px !important;
+    height: 25px !important;
+    line-height: 25px !important;
+    padding: 2px;
+    min-width: 0;
+    flex-shrink: 0;
+    border-radius: 50%;
+    margin: 0 2px 0 2px;
+}
+
 li {
     color: darkgrey;
     font-size: 13px;
@@ -65,11 +83,41 @@ li {
 }
 
 .node-header-avatar {
-    /*background-image: url('https://material.angular.io/assets/img/examples/shiba1.jpg');*/
-    color: darkgray;
+    color: $sp-color-accent-light;
     padding-top: 6px;
 }
 
+.div-icon-node-actions {
+    margin-left: auto;
+    margin-right: 0;
+}
+
+.node-state {
+    //float:right;
+    transform: scale(1.3);
+}
+
+.node-activated {
+    @extend .node-state;
+    color: $sp-color-primary;
+}
+
+.node-inactive {
+    @extend .node-state;
+    color: $sp-color-accent-light;
+}
+
+.node-deactivated {
+    @extend .node-state;
+    //color: $sp-color-accent;
+    color: #F44336;
+}
+
+.node-evict {
+    @extend .node-state;
+    color: $sp-color-accent;
+}
+
 /*.ft-foot-4 {*/
 /*    column-count: 4;*/
 /*    float: left;*/
@@ -111,4 +159,8 @@ hr {
     border-top: 1px solid #ccc;
     margin: 0.6em 3em;
     padding: 0;
+}
+
+.fa-custom {
+    font-size: 2em;
 }
\ No newline at end of file
diff --git a/ui/src/app/configuration/node-configuration/node-configuration.component.ts b/ui/src/app/configuration/node-configuration/node-configuration.component.ts
new file mode 100644
index 0000000..1728124
--- /dev/null
+++ b/ui/src/app/configuration/node-configuration/node-configuration.component.ts
@@ -0,0 +1,169 @@
+/*
+ * 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, OnInit, ViewEncapsulation} from "@angular/core";
+import {
+    FieldDeviceAccessResource,
+    NodeInfoDescription,
+    NvidiaContainerRuntime
+} from "../../core-model/gen/streampipes-model";
+import {MatSnackBar} from "@angular/material/snack-bar";
+import {zip} from "rxjs";
+import {DialogService} from "../../core-ui/dialog/base-dialog/base-dialog.service";
+import {PipelineService} from "../../platform-services/apis/pipeline.service";
+import {DataMarketplaceService} from "../../connect/services/data-marketplace.service";
+import {NodeService} from "../../platform-services/apis/node.service";
+import {PanelType} from "../../core-ui/dialog/base-dialog/base-dialog.model";
+import {NodeConfigurationDetailsComponent} from "./node-configuration-details/node-configuration-details.component";
+
+@Component({
+    selector: 'node-configuration',
+    encapsulation: ViewEncapsulation.None,
+    templateUrl: './node-configuration.component.html',
+    styleUrls: ['./node-configuration.component.scss']
+})
+export class NodeConfigurationComponent implements OnInit{
+
+    loadingCompleted: boolean = false;
+    nodes: NodeInfoDescription[];
+
+    os: String;
+    serverVersion: String;
+    availableGPU: boolean = false;
+    cpuCores: number;
+    cpuArch: string;
+    memTotal: number;
+    diskTotal: number;
+    gpuCores: number;
+    gpuType: string;
+    locationTags: String[];
+    fieldDevices: FieldDeviceAccessResource[];
+
+    constructor(private nodeService: NodeService,
+                private dataMarketplaceService: DataMarketplaceService,
+                private DialogService: DialogService,
+                private pipelineService: PipelineService,
+                private _snackBar: MatSnackBar) { }
+
+    ngOnInit() {
+        this.getNodes();
+    }
+
+    getNodes() {
+        this.nodeService.getNodes().subscribe(response => {
+            this.nodes = response;
+            this.nodes.forEach(x => {
+                // Raspbian is too long -> shorten it
+                let os = x.nodeResources.softwareResource.os;
+                if (os.includes('Raspbian')) {
+                    if (os.includes('buster')) {
+                        if (os.includes('10')) {
+                            x.nodeResources.softwareResource.os = 'Raspbian 10 (buster)'
+                        }
+                    }
+                }
+            });
+        })
+    }
+
+    bytesToGB(bytes) {
+        var b = 1;
+        var kb = b * 1024;
+        var mb = kb * 1024;
+        var gb = mb * 1024;
+        return (Math.round((bytes / gb) * 100) / 100);
+    };
+
+    nvidiaRuntime(node: NodeInfoDescription) {
+        let nvidiaRuntime = false;
+        if (node.nodeResources.softwareResource.containerRuntime instanceof NvidiaContainerRuntime) {
+            nvidiaRuntime = true;
+        }
+        return nvidiaRuntime;
+    }
+
+    async changeNodeState(node: NodeInfoDescription, desiredState: boolean) {
+        if (node.active != desiredState) {
+            var detectedProcessors = await this.checkNodeForProcessors(node.nodeControllerId);
+            if (!detectedProcessors) {
+                // No processors detected on this node. This means that no pipeline exists, that hosts processors on
+                // this node. However, there can still be active adapters that running on that node that need be checked
+                var detectedAdapters = await this.checkNodeForAdapters(node.nodeControllerId);
+                if (!detectedAdapters) {
+                    // No adapters detected on this node. This means that no adapter was created on this host. Thus
+                    // we can safely proceed setting a new state, i.e. active = (true || false) this node
+                    node.active = desiredState;
+                    this.nodeService.updateNodeState(node).subscribe(statusMessage => {
+                        if(statusMessage.success) {
+                            this.openSnackBar("Node successfully " + (desiredState ? "activated" : "deactivated"))
+                        }
+                    });
+                } else {
+                    this.openSnackBar("At least one adapter is executed on that node. Aborted!")
+                }
+            } else {
+                this.openSnackBar("At least one processor is executed on that node. Aborted!")
+            }
+        }
+    }
+
+    checkNodeForProcessors(nodeControllerId: string) {
+        return new Promise<boolean>(resolve => {
+            var detectedProcessors = false;
+            zip(this.pipelineService.getOwnPipelines(),
+                this.pipelineService.getSystemPipelines()).subscribe(allPipelines => {
+                allPipelines.forEach((pipelines, index) => {
+                    pipelines.forEach(pipeline => {
+                        detectedProcessors = pipeline.running && pipeline
+                            .sepas.some(sepa => sepa.deploymentTargetNodeId === nodeControllerId);
+                    })
+                })
+                resolve(detectedProcessors);
+            });
+        });
+    };
+
+    checkNodeForAdapters(nodeControllerId: string) {
+        return new Promise<boolean>(resolve => {
+            this.dataMarketplaceService.getAdapters().subscribe(allAdapters => {
+                resolve(allAdapters.some(adapter => adapter.deploymentTargetNodeId === nodeControllerId));
+            });
+        })
+    }
+
+    openSnackBar(message: string) {
+        this._snackBar.open(message, "close", {
+            duration: 3000,
+        });
+    }
+
+    evict(node: NodeInfoDescription) {
+        // TODO: placeholder to migrate processors and adapters to another node
+        this.openSnackBar("evict all adapters and processors");
+    }
+
+    settings(node: NodeInfoDescription) {
+        this.DialogService.open(NodeConfigurationDetailsComponent,{
+            panelType: PanelType.SLIDE_IN_PANEL,
+            title: "Edit Node configuration",
+            data: {
+                "node": node
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/configuration/shared/configuration.service.ts b/ui/src/app/configuration/shared/configuration.service.ts
index df9d919..330abba 100644
--- a/ui/src/app/configuration/shared/configuration.service.ts
+++ b/ui/src/app/configuration/shared/configuration.service.ts
@@ -47,15 +47,6 @@ export class ConfigurationService {
             )
     }
 
-    getAvailableEdgeNodes(): Observable<NodeInfoDescription[]> {
-        return this.http.get(this.getServerUrl() + '/api/v2/users/' + this.authStatusService.email + "/nodes")
-            .pipe(
-                map(response => {
-                    return response as NodeInfoDescription[];
-                })
-            )
-    }
-
     getConsulServices(): Observable<StreampipesPeContainer[]> {
         return this.http.get(this.getServerUrl() + '/api/v2/consul')
             .pipe(
diff --git a/ui/src/app/core-model/gen/streampipes-model.ts b/ui/src/app/core-model/gen/streampipes-model.ts
index 0d64d16..2650545 100644
--- a/ui/src/app/core-model/gen/streampipes-model.ts
+++ b/ui/src/app/core-model/gen/streampipes-model.ts
@@ -19,10 +19,10 @@
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 2.24.612 on 2020-12-23 14:13:03.
+// Generated using typescript-generator version 2.24.612 on 2020-12-30 22:52:01.
 
 export class AbstractStreamPipesEntity {
-    "@class": "org.apache.streampipes.model.base.NamedStreamPipesEntity" | "org.apache.streampipes.model.connect.adapter.AdapterDescription" | "org.apache.streampipes.model.connect.adapter.AdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.GenericAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.SpecificAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.AdapterStreamDescription" | "org.apache.streampipes.model.connect.adapter.G [...]
+    "@class": "org.apache.streampipes.model.base.NamedStreamPipesEntity" | "org.apache.streampipes.model.connect.adapter.AdapterDescription" | "org.apache.streampipes.model.connect.adapter.AdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.GenericAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.SpecificAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.AdapterStreamDescription" | "org.apache.streampipes.model.connect.adapter.G [...]
 
     static fromData(data: AbstractStreamPipesEntity, target?: AbstractStreamPipesEntity): AbstractStreamPipesEntity {
         if (!data) {
@@ -35,7 +35,7 @@ export class AbstractStreamPipesEntity {
 }
 
 export class UnnamedStreamPipesEntity extends AbstractStreamPipesEntity {
-    "@class": "org.apache.streampipes.model.base.UnnamedStreamPipesEntity" | "org.apache.streampipes.model.connect.guess.GuessSchema" | "org.apache.streampipes.model.connect.rules.TransformationRuleDescription" | "org.apache.streampipes.model.connect.rules.value.ValueTransformationRuleDescription" | "org.apache.streampipes.model.connect.rules.value.AddTimestampRuleDescription" | "org.apache.streampipes.model.connect.rules.value.AddValueTransformationRuleDescription" | "org.apache.streamp [...]
+    "@class": "org.apache.streampipes.model.base.UnnamedStreamPipesEntity" | "org.apache.streampipes.model.connect.guess.GuessSchema" | "org.apache.streampipes.model.connect.rules.TransformationRuleDescription" | "org.apache.streampipes.model.connect.rules.value.ValueTransformationRuleDescription" | "org.apache.streampipes.model.connect.rules.value.AddTimestampRuleDescription" | "org.apache.streampipes.model.connect.rules.value.AddValueTransformationRuleDescription" | "org.apache.streamp [...]
     elementId: string;
 
     static fromData(data: UnnamedStreamPipesEntity, target?: UnnamedStreamPipesEntity): UnnamedStreamPipesEntity {
@@ -2206,13 +2206,16 @@ export class NodeBrokerDescription extends UnnamedStreamPipesEntity {
 
 export class NodeInfoDescription extends UnnamedStreamPipesEntity {
     "@class": "org.apache.streampipes.model.node.NodeInfoDescription";
+    _id: string;
+    _rev: string;
+    active: boolean;
     hostname: string;
     nodeBroker: NodeBrokerDescription;
     nodeControllerId: string;
     nodeResources: NodeResource;
     port: number;
     registeredContainers: DeploymentContainerUnion[];
-    staticNodeMedata: StaticNodeMedata;
+    staticNodeMetadata: StaticNodeMetadata;
     supportedElements: string[];
 
     static fromData(data: NodeInfoDescription, target?: NodeInfoDescription): NodeInfoDescription {
@@ -2221,14 +2224,17 @@ export class NodeInfoDescription extends UnnamedStreamPipesEntity {
         }
         const instance = target || new NodeInfoDescription();
         super.fromData(data, instance);
+        instance.active = data.active;
         instance.nodeControllerId = data.nodeControllerId;
         instance.hostname = data.hostname;
         instance.port = data.port;
         instance.nodeBroker = NodeBrokerDescription.fromData(data.nodeBroker);
-        instance.staticNodeMedata = StaticNodeMedata.fromData(data.staticNodeMedata);
+        instance.staticNodeMetadata = StaticNodeMetadata.fromData(data.staticNodeMetadata);
+        instance.nodeResources = NodeResource.fromData(data.nodeResources);
         instance.registeredContainers = __getCopyArrayFn(DeploymentContainer.fromDataUnion)(data.registeredContainers);
         instance.supportedElements = __getCopyArrayFn(__identity<string>())(data.supportedElements);
-        instance.nodeResources = NodeResource.fromData(data.nodeResources);
+        instance._id = data._id;
+        instance._rev = data._rev;
         return instance;
     }
 }
@@ -2927,18 +2933,18 @@ export class SpecificAdapterStreamDescription extends AdapterStreamDescription {
     }
 }
 
-export class StaticNodeMedata extends UnnamedStreamPipesEntity {
-    "@class": "org.apache.streampipes.model.node.meta.StaticNodeMedata";
+export class StaticNodeMetadata extends UnnamedStreamPipesEntity {
+    "@class": "org.apache.streampipes.model.node.meta.StaticNodeMetadata";
     geoLocation: GeoLocation;
     locationTags: string[];
     model: string;
     type: string;
 
-    static fromData(data: StaticNodeMedata, target?: StaticNodeMedata): StaticNodeMedata {
+    static fromData(data: StaticNodeMetadata, target?: StaticNodeMetadata): StaticNodeMetadata {
         if (!data) {
             return data;
         }
-        const instance = target || new StaticNodeMedata();
+        const instance = target || new StaticNodeMetadata();
         super.fromData(data, instance);
         instance.type = data.type;
         instance.model = data.model;
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 be84a50..f8ac965 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
@@ -31,6 +31,7 @@ import {ConfirmDialogComponent} from "../../../core-ui/dialog/confirm-dialog/con
 import {MatDialog} from "@angular/material/dialog";
 import {EditorService} from "../../services/editor.service";
 import {PipelineService} from "../../../platform-services/apis/pipeline.service";
+import {pipe} from "rxjs";
 
 
 @Component({
@@ -51,6 +52,7 @@ export class PipelineAssemblyComponent implements OnInit {
     selectMode: any;
     currentPipelineName: any;
     currentPipelineDescription: any;
+    currentEventRelayStrategy: any;
 
     @Input()
     currentModifiedPipelineId: any;
@@ -188,6 +190,7 @@ export class PipelineAssemblyComponent implements OnInit {
 
         pipeline.name = this.currentPipelineName;
         pipeline.description = this.currentPipelineDescription;
+        pipeline.eventRelayStrategy = this.currentEventRelayStrategy;
         if (this.currentModifiedPipelineId) {
             pipeline._id = this.currentModifiedPipelineId;
         }
@@ -217,6 +220,7 @@ export class PipelineAssemblyComponent implements OnInit {
                 let pipeline = msg;
                 this.currentPipelineName = pipeline.name;
                 this.currentPipelineDescription = pipeline.description;
+                this.currentEventRelayStrategy = pipeline.eventRelayStrategy;
                 this.rawPipelineModel = this.JsplumbService.makeRawPipeline(pipeline, false);
                 this.displayPipelineInEditor(true);
             });
diff --git a/ui/src/app/editor/dialog/migrate-pipeline-processors/migrate-pipeline-processors.component.ts b/ui/src/app/editor/dialog/migrate-pipeline-processors/migrate-pipeline-processors.component.ts
index ce15aed..b555991 100644
--- a/ui/src/app/editor/dialog/migrate-pipeline-processors/migrate-pipeline-processors.component.ts
+++ b/ui/src/app/editor/dialog/migrate-pipeline-processors/migrate-pipeline-processors.component.ts
@@ -3,13 +3,14 @@ import {
   DataProcessorInvocation, Message,
   NodeInfoDescription,
   Pipeline,
-  StaticNodeMedata
+  StaticNodeMetadata
 } from "../../../core-model/gen/streampipes-model";
 import {FormControl, FormGroup, Validators} from "@angular/forms";
 import {EditorService} from "../../services/editor.service";
 import {DialogRef} from "../../../core-ui/dialog/base-dialog/dialog-ref";
 import {ObjectProvider} from "../../services/object-provider.service";
 import {PipelineService} from "../../../platform-services/apis/pipeline.service";
+import {NodeService} from "../../../platform-services/apis/node.service";
 
 @Component({
   selector: 'migrate-pipeline-processors',
@@ -40,7 +41,8 @@ export class MigratePipelineProcessorsComponent implements OnInit {
   constructor(private editorService: EditorService,
               private dialogRef: DialogRef<MigratePipelineProcessorsComponent>,
               private objectProvider: ObjectProvider,
-              private pipelineService: PipelineService) {
+              private pipelineService: PipelineService,
+              private nodeService: NodeService) {
 
     this.advancedSettings = true;
     this.panelOpenState = true;
@@ -90,7 +92,7 @@ export class MigratePipelineProcessorsComponent implements OnInit {
   }
 
   loadAndPrepareEdgeNodes() {
-    this.pipelineService.getAvailableEdgeNodes().subscribe(response => {
+    this.nodeService.getAvailableNodes().subscribe(response => {
       this.edgeNodes = response;
       this.addAppIds(this.tmpPipeline.sepas, this.edgeNodes);
       this.addAppIds(this.tmpPipeline.actions, this.edgeNodes);
@@ -120,9 +122,9 @@ export class MigratePipelineProcessorsComponent implements OnInit {
     let nodeInfo = {} as NodeInfoDescription;
     nodeInfo.nodeControllerId = "default";
     nodeInfo.hostname = "default";
-    nodeInfo.staticNodeMedata = {} as StaticNodeMedata;
-    nodeInfo.staticNodeMedata.type = "default";
-    nodeInfo.staticNodeMedata.model = "Default Node";
+    nodeInfo.staticNodeMetadata = {} as StaticNodeMetadata;
+    nodeInfo.staticNodeMetadata.type = "default";
+    nodeInfo.staticNodeMetadata.model = "Default Node";
     return nodeInfo;
   }
 
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 1b643bc..fef866c 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
@@ -23,7 +23,7 @@ import {
   Message,
   Pipeline,
   NodeInfoDescription,
-  StaticNodeMedata
+  StaticNodeMetadata
 } from "../../../core-model/gen/streampipes-model";
 import {ObjectProvider} from "../../services/object-provider.service";
 import {EditorService} from "../../services/editor.service";
@@ -31,6 +31,7 @@ import {PipelineService} from "../../../platform-services/apis/pipeline.service"
 import {ShepherdService} from "../../../services/tour/shepherd.service";
 import {FormControl, FormGroup, Validators} from "@angular/forms";
 import {Router} from "@angular/router";
+import {NodeService} from "../../../platform-services/apis/node.service";
 
 @Component({
   selector: 'save-pipeline',
@@ -71,6 +72,7 @@ export class SavePipelineComponent implements OnInit {
               private dialogRef: DialogRef<SavePipelineComponent>,
               private objectProvider: ObjectProvider,
               private pipelineService: PipelineService,
+              private nodeService: NodeService,
               private Router: Router,
               private ShepherdService: ShepherdService) {
     this.pipelineCategories = [];
@@ -101,10 +103,16 @@ export class SavePipelineComponent implements OnInit {
       this.ShepherdService.trigger("enter-pipeline-name");
     }
 
-    this.selectedRelayStrategyVal = "buffer";
-    this.selectedPipelineExecutionPolicy = "locality-aware";
-
-    this.applyLocalityAwarePolicy(this.tmpPipeline.streams, this.tmpPipeline.sepas);
+    if (this.currentModifiedPipelineId && this.updateMode === 'update') {
+      this.selectedRelayStrategyVal = this.tmpPipeline.eventRelayStrategy;
+      this.selectedPipelineExecutionPolicy = "custom";
+      this.panelOpenState = true;
+      this.disableNodeSelection.setValue(false);
+    } else {
+      this.selectedRelayStrategyVal = "buffer";
+      this.selectedPipelineExecutionPolicy = "locality-aware";
+      this.applyLocalityAwarePolicy(this.tmpPipeline.streams, this.tmpPipeline.sepas);
+    }
   }
 
   deepCopy<T>(source: T): T {
@@ -158,7 +166,7 @@ export class SavePipelineComponent implements OnInit {
   }
 
   loadAndPrepareEdgeNodes() {
-    this.pipelineService.getAvailableEdgeNodes().subscribe(response => {
+    this.nodeService.getAvailableNodes().subscribe(response => {
       this.edgeNodes = response;
       this.addAppIds(this.tmpPipeline.sepas, this.edgeNodes);
       this.addAppIds(this.tmpPipeline.actions, this.edgeNodes);
@@ -195,9 +203,9 @@ export class SavePipelineComponent implements OnInit {
     let nodeInfo = {} as NodeInfoDescription;
     nodeInfo.nodeControllerId = "default";
     nodeInfo.hostname = "default";
-    nodeInfo.staticNodeMedata = {} as StaticNodeMedata;
-    nodeInfo.staticNodeMedata.type = "default";
-    nodeInfo.staticNodeMedata.model = "Default Node";
+    nodeInfo.staticNodeMetadata = {} as StaticNodeMetadata;
+    nodeInfo.staticNodeMetadata.type = "default";
+    nodeInfo.staticNodeMetadata.model = "Default Node";
     return nodeInfo;
   }
 
@@ -236,15 +244,15 @@ export class SavePipelineComponent implements OnInit {
     let storageRequest;
 
     if (this.currentModifiedPipelineId && this.updateMode === 'update') {
-      this.modifyPipelineElementsDeployments(this.tmpPipeline.sepas)
-      this.modifyPipelineElementsDeployments(this.tmpPipeline.actions)
+      this.modifyPipelineElementsDeployments(this.tmpPipeline.sepas);
+      this.modifyPipelineElementsDeployments(this.tmpPipeline.actions);
       this.tmpPipeline.eventRelayStrategy = this.selectedRelayStrategyVal;
       this.pipeline = this.tmpPipeline;
       storageRequest = this.pipelineService.updatePipeline(this.pipeline);
     } else {
       this.pipeline._id = undefined;
-      this.modifyPipelineElementsDeployments(this.tmpPipeline.sepas)
-      this.modifyPipelineElementsDeployments(this.tmpPipeline.actions)
+      this.modifyPipelineElementsDeployments(this.tmpPipeline.sepas);
+      this.modifyPipelineElementsDeployments(this.tmpPipeline.actions);
       this.tmpPipeline.eventRelayStrategy = this.selectedRelayStrategyVal;
       this.pipeline = this.tmpPipeline;
       storageRequest = this.pipelineService.storePipeline(this.pipeline);
diff --git a/ui/src/app/editor/services/object-provider.service.ts b/ui/src/app/editor/services/object-provider.service.ts
index 1562e55..0422cfc 100644
--- a/ui/src/app/editor/services/object-provider.service.ts
+++ b/ui/src/app/editor/services/object-provider.service.ts
@@ -40,6 +40,7 @@ export class ObjectProvider {
         var pipeline = new Pipeline();
         pipeline.name = "";
         pipeline.description = "";
+        pipeline.eventRelayStrategy = "";
         pipeline.streams = [];
         pipeline.sepas = [];
         pipeline.actions = [];
diff --git a/ui/src/app/platform-services/apis/node.service.ts b/ui/src/app/platform-services/apis/node.service.ts
new file mode 100644
index 0000000..290d36a
--- /dev/null
+++ b/ui/src/app/platform-services/apis/node.service.ts
@@ -0,0 +1,55 @@
+/*
+ * 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 {Injectable} from "@angular/core";
+import {HttpClient} from "@angular/common/http";
+import {PlatformServicesCommons} from "./commons.service";
+import {Observable} from "rxjs";
+import {
+    Message, NodeInfoDescription,
+} from "../../core-model/gen/streampipes-model";
+import {map} from "rxjs/operators";
+
+@Injectable()
+export class NodeService {
+
+    constructor(private http: HttpClient,
+                private platformServicesCommons: PlatformServicesCommons) {
+    }
+
+    getNodes(): Observable<NodeInfoDescription[]> {
+        return this.http.get(this.platformServicesCommons.authUserBasePath() + "/nodes")
+            .pipe(map(response => {
+                return response as NodeInfoDescription[];
+            }));
+    }
+
+    getAvailableNodes(): Observable<NodeInfoDescription[]> {
+        return this.http.get(this.platformServicesCommons.authUserBasePath() + "/nodes/available")
+            .pipe(map(response => {
+                return response as NodeInfoDescription[];
+            }));
+    }
+
+    updateNodeState(node: NodeInfoDescription): Observable<Message> {
+        return this.http.put(this.platformServicesCommons.authUserBasePath() + '/nodes/' + node.nodeControllerId, node)
+            .pipe(map(response => {
+                return Message.fromData(response as Message);
+            }));
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/platform-services/apis/pipeline.service.ts b/ui/src/app/platform-services/apis/pipeline.service.ts
index a2f8de8..3c1c312 100644
--- a/ui/src/app/platform-services/apis/pipeline.service.ts
+++ b/ui/src/app/platform-services/apis/pipeline.service.ts
@@ -111,12 +111,4 @@ export class PipelineService {
           return (response as any[]).map(r => PipelineStatusMessage.fromData(r));
         }));
   }
-
-  getAvailableEdgeNodes(): Observable<NodeInfoDescription[]> {
-    return this.http.get(this.platformServicesCommons.authUserBasePath() + "/nodes")
-        .pipe(map(response => {
-          return response as NodeInfoDescription[];
-    }));
-  }
-
 }
\ No newline at end of file
diff --git a/ui/src/app/platform-services/platform.module.ts b/ui/src/app/platform-services/platform.module.ts
index e96fc12..b09c0ad 100644
--- a/ui/src/app/platform-services/platform.module.ts
+++ b/ui/src/app/platform-services/platform.module.ts
@@ -22,6 +22,7 @@ import {PipelineService} from "./apis/pipeline.service";
 import {PlatformServicesCommons} from "./apis/commons.service";
 import {PipelineElementEndpointService} from "./apis/pipeline-element-endpoint.service";
 import {FilesService} from "./apis/files.service";
+import {NodeService} from "./apis/node.service";
 
 @NgModule({
   imports: [],
@@ -32,7 +33,8 @@ import {FilesService} from "./apis/files.service";
     PipelineElementEndpointService,
     //PipelineTemplateService,
     PipelineElementService,
-    PipelineService
+    PipelineService,
+    NodeService,
   ],
   entryComponents: []
 })