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/03/17 14:13:36 UTC

[incubator-streampipes] 01/02: Add retry option when pipeline element container not ready

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 21e2c945b0a0a448fcd67d0491e914873725d782
Merge: 45b1f3c 2f10a17
Author: Patrick Wiener <wi...@fzi.de>
AuthorDate: Wed Mar 17 13:08:09 2021 +0100

    Add retry option when pipeline element container not ready

 .../backend/StreamPipesBackendApplication.java     |  30 ++-
 .../api/AbstractPipelineElementResource.java       |  14 ++
 .../api/InvocablePipelineElementResource.java      |  42 ++--
 .../container/init/RunningInstances.java           |   4 +
 .../dataexplorer/DataLakeManagementV3.java         |   2 +-
 .../management/pe/InvocableElementManager.java     |  58 ++++--
 .../controller/container/storage/MapDBImpl.java    |   6 +-
 ui/angular.json                                    |   2 -
 ui/package.json                                    |   5 +-
 .../node-configuration.component.scss              |   1 +
 .../pipeline-assembly.component.html               | 112 +++++++----
 .../pipeline-assembly.component.scss               | 121 +++++++++++-
 .../pipeline-assembly.component.ts                 | 143 ++++++++++----
 .../pipeline-element-options.component.ts          |   8 +-
 .../components/pipeline/pipeline.component.html    |  10 +-
 .../components/pipeline/pipeline.component.ts      |  59 ++++--
 ui/src/app/editor/editor.component.html            |  37 ++--
 ui/src/app/editor/editor.component.scss            |   6 +-
 ui/src/app/editor/editor.component.ts              |  12 +-
 ui/src/app/editor/editor.module.ts                 |  16 +-
 .../enabled-pipeline-element.filter.ts}            |  23 ++-
 ui/src/app/editor/model/editor.model.ts            |   7 +-
 .../jsplumb.model.ts}                              |  19 +-
 .../app/editor/services/jsplumb-bridge.service.ts  |  79 +++++---
 .../app/editor/services/jsplumb-config.service.ts  |  48 ++---
 .../editor/services/jsplumb-endpoint.service.ts    |  63 ++++++
 .../app/editor/services/jsplumb-factory.service.ts |  85 ++++++++
 ui/src/app/editor/services/jsplumb.service.ts      | 219 +++++++++------------
 .../app/editor/services/object-provider.service.ts |  24 ++-
 .../pipeline-canvas-scrolling.service.ts}          |  24 ++-
 .../app/editor/services/pipeline-editor.service.ts |  19 +-
 .../pipeline-element-dragged.service.ts}           |  20 +-
 .../pipeline-element-recommendation.service.ts     |   8 +-
 .../services/pipeline-positioning.service.ts       | 124 ++++++------
 .../editor/services/pipeline-validation.service.ts |  42 ++--
 .../preview/pipeline-preview.component.ts          |   8 +-
 ui/src/scss/_variables.scss                        |   3 +-
 ui/src/scss/main.scss                              |   6 +-
 .../editor.component.scss => scss/sp/jsplumb.scss} |  16 +-
 ui/src/scss/sp/main.scss                           |  33 +---
 40 files changed, 999 insertions(+), 559 deletions(-)

diff --cc streampipes-container/src/main/java/org/apache/streampipes/container/api/InvocablePipelineElementResource.java
index a56a7d3,4530f8a..0b55885
--- a/streampipes-container/src/main/java/org/apache/streampipes/container/api/InvocablePipelineElementResource.java
+++ b/streampipes-container/src/main/java/org/apache/streampipes/container/api/InvocablePipelineElementResource.java
@@@ -70,15 -72,19 +72,19 @@@ public abstract class InvocablePipeline
                      RunningInstances.INSTANCE.add(runningInstanceId, graph, declarer.getClass().newInstance());
                      Response resp = RunningInstances.INSTANCE.getInvocation(runningInstanceId).invokeRuntime(graph);
                      return ok(resp);
+                 } else {
+                     LOG.info("Pipeline element {} with id {} seems to be already running, skipping invocation request.", graph.getName(), runningInstanceId);
 -                    Response resp = new Response(graph.getElementId(), true);
++                    Response resp = new Response(graph.getElementId(), true, "Pipeline element already running");
+                     return ok(resp);
                  }
-             } catch (InstantiationException | IllegalAccessException e) {
-                 e.printStackTrace();
-                 return ok(new Response(elementId, false, e.getMessage()));
-             }
  
-             return ok(new Response(elementId, false, "Could not find the element with id: " + elementId));
+             }
+         } catch (InstantiationException | IllegalAccessException e) {
+             e.printStackTrace();
+             return ok(new Response(elementId, false, e.getMessage()));
          }
-         return ok(new Response(elementId, true, "Already running element with id: " + elementId));
+ 
+         return ok(new Response(elementId, false, "Could not find the element with id: " + elementId));
      }
  
      @POST
diff --cc streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/management/pe/InvocableElementManager.java
index 04cf003,0000000..848f243
mode 100644,000000..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
@@@ -1,301 -1,0 +1,333 @@@
 +/*
 + * 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.node.controller.container.management.pe;
 +
 +import com.fasterxml.jackson.core.JsonProcessingException;
 +import com.google.gson.JsonSyntaxException;
 +import org.apache.http.client.fluent.Request;
 +import org.apache.http.entity.ContentType;
 +import org.apache.http.entity.StringEntity;
 +import org.apache.streampipes.commons.exceptions.SpRuntimeException;
 +import org.apache.streampipes.container.model.node.InvocableRegistration;
 +import org.apache.streampipes.messaging.EventProducer;
 +import org.apache.streampipes.messaging.jms.ActiveMQPublisher;
 +import org.apache.streampipes.messaging.kafka.SpKafkaProducer;
 +import org.apache.streampipes.messaging.mqtt.MqttPublisher;
 +import org.apache.streampipes.model.Response;
 +import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
 +import org.apache.streampipes.model.grounding.*;
 +import org.apache.streampipes.model.node.NodeInfoDescription;
 +import org.apache.streampipes.model.pipeline.PipelineElementReconfigurationEntity;
 +import org.apache.streampipes.model.staticproperty.FreeTextStaticProperty;
 +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;
 +import org.slf4j.LoggerFactory;
 +
 +import java.io.IOException;
++import java.net.InetSocketAddress;
 +import java.net.MalformedURLException;
++import java.net.Socket;
 +import java.net.URL;
 +import java.nio.charset.StandardCharsets;
 +import java.util.Collections;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +
 +public class InvocableElementManager implements PipelineElementLifeCycle {
 +
 +    private static final Logger LOG =
 +            LoggerFactory.getLogger(InvocableElementManager.class.getCanonicalName());
 +
 +    private static final String HTTP_PROTOCOL = "http://";
 +    private static final String COLON = ":";
 +    private static final String SLASH = "/";
 +    private static final String DOT = ".";
 +    private static final String SP_URL = "SP_URL";
 +    private static final String CONSUL_LOCATION = "CONSUL_LOCATION";
 +    private static final String CONSUL_REGISTRATION_ROUTE = "v1/agent/service/register";
 +    private static final String RECONFIGURATION_TOPIC = "org.apache.streampipes.control.event.reconfigure";
 +    private static final int CONSUL_DEFAULT_PORT = 8500;
-     private static final Integer CONNECT_TIMEOUT = 10000;
++    private static final Integer CONNECT_TIMEOUT_MS = 10000;
++    private static final long RETRY_INTERVAL_MS = 5000;
 +    private static InvocableElementManager instance = null;
 +
 +    private InvocableElementManager() {}
 +
 +    public static InvocableElementManager getInstance() {
 +        if (instance == null) {
 +            synchronized (InvocableElementManager.class) {
 +                if (instance == null)
 +                    instance = new InvocableElementManager();
 +            }
 +        }
 +        return instance;
 +    }
 +
 +    @Override
 +    public void register(InvocableRegistration registration) {
 +        registerAtConsul(registration);
 +        updateAndSyncNodeInfoDescription(registration);
 +        invokePipelineElementsOnSystemRebootOrRestart();
 +        LOG.info("Successfully registered pipeline element container");
 +    }
 +
 +    @Override
 +    public Response invoke(InvocableStreamPipesEntity graph) {
++
++        Response response = new Response();
 +        String endpoint = graph.getBelongsTo();
-         LOG.info("Invoke pipeline element: {}", endpoint);
-         try {
-             org.apache.http.client.fluent.Response httpResp = Request
-                     .Post(endpoint)
-                     .bodyString(toJson(graph), ContentType.APPLICATION_JSON)
-                     .connectTimeout(CONNECT_TIMEOUT)
-                     .execute();
-             return handleResponse(httpResp);
-         } catch (Exception e) {
-             LOG.error(e.getMessage());
++        String body = toJson(graph);
++        LOG.info("Trying to invoke pipeline element: {}", endpoint);
++
++        boolean connected = false;
++        while (!connected) {
++
++            connected = post(endpoint, body, response);
++
++            if (!connected) {
++                LOG.info("Retrying in {} seconds", (RETRY_INTERVAL_MS / 1000));
++                try {
++                    Thread.sleep(RETRY_INTERVAL_MS);
++                } catch (InterruptedException e) {
++                    throw new RuntimeException("Failed to invoke pipeline element: " + endpoint);
++                }
++            }
 +        }
-         throw new RuntimeException("Failed to invoke pipeline element: " + endpoint);
++        LOG.info("Successfully invoked pipeline element {}", endpoint);
++        return response;
 +    }
 +
 +    @Override
 +    public Response detach(String endpoint) {
 +        LOG.info("Detach pipeline element: {}", endpoint);
 +        try {
 +            org.apache.http.client.fluent.Response httpResp = Request
 +                    .Delete(endpoint)
-                     .connectTimeout(CONNECT_TIMEOUT)
++                    .connectTimeout(CONNECT_TIMEOUT_MS)
 +                    .execute();
 +            return handleResponse(httpResp);
 +        } catch (Exception e) {
 +            LOG.error(e.getMessage());
 +        }
 +        throw new IllegalArgumentException("Failed to detach pipeline element: " + endpoint);
 +    }
 +
 +    @Override
 +    public void unregister(){
 +        // TODO: unregister element from Consul and
 +        setSupportedPipelineElements(Collections.emptyList());
 +        try {
 +            String url = generateBackendEndpoint();
 +            String desc = toJson(getNodeInfoDescription());
 +            Request.Put(url)
 +                    .bodyString(desc, ContentType.APPLICATION_JSON)
 +                    .execute();
 +        } catch (IOException e) {
 +            e.printStackTrace();
 +        }
 +    }
 +
 +    @Override
 +    public Response adapt(InvocableStreamPipesEntity graph, PipelineElementReconfigurationEntity reconfigurationEntity) {
 +
 +        Response response = new Response();
 +        response.setElementId(graph.getElementId());
 +        response.setSuccess(false);
 +
 +        EventProducer pub = getReconfigurationEventProducerFromInvocable(graph);
 +
 +        byte [] reconfigurationEvent = reconfigurationToByteArray(reconfigurationEntity);
 +        pub.publish(reconfigurationEvent);
 +        pub.disconnect();
 +
 +        response.setSuccess(true);
 +        return response;
 +    }
 +
++    private boolean post(String endpoint, String body, Response response) {
++        try {
++            org.apache.http.client.fluent.Response httpResp = Request
++                    .Post(endpoint)
++                    .bodyString(body, ContentType.APPLICATION_JSON)
++                    .connectTimeout(CONNECT_TIMEOUT_MS)
++                    .execute();
++            Response resp = handleResponse(httpResp);
++
++            response.setElementId(resp.getElementId());
++            response.setSuccess(resp.isSuccess());
++            response.setOptionalMessage(resp.getOptionalMessage());
++
++            return true;
++        } catch (IOException e) {
++            LOG.error("Could not invoke pipeline element - is the pipeline element container ready?");
++        }
++        return false;
++    }
++
 +    public void invokePipelineElementsOnSystemRebootOrRestart() {
 +        LOG.info("Checking for pipeline elements to be started ...");
 +        getAllInvocables()
 +                .forEach(graph -> {
 +                    Response status = InvocableElementManager.getInstance().invoke(graph);
 +                    if (status.isSuccess()) {
 +                        if (status.getOptionalMessage().isEmpty()) {
 +                            LOG.info("Pipeline element successfully restarted {}", status.getElementId());
 +                        } else {
 +                            LOG.info("Pipeline element already running {}", status.getElementId());
 +                        }
 +                    } else {
 +                        LOG.error("Pipeline element could not be restarted - are the pipeline element containers " +
 +                                "running? {}", status.getElementId());
 +                    }
 +                });
 +    }
 +
 +    private EventProducer getReconfigurationEventProducerFromInvocable(InvocableStreamPipesEntity graph) {
 +        TransportProtocol tp = getReconfigurationTransportProtocol(graph);
 +        EventProducer pub;
 +        if(tp instanceof KafkaTransportProtocol){
 +            pub = new SpKafkaProducer();
 +            pub.connect(tp);
 +        } else if (tp instanceof JmsTransportProtocol){
 +            pub = new ActiveMQPublisher();
 +            pub.connect(tp);
 +        } else{
 +            pub = new MqttPublisher();
 +            pub.connect(tp);
 +        }
 +        return pub;
 +    }
 +
 +    private byte[] reconfigurationToByteArray(PipelineElementReconfigurationEntity entity) {
 +        Map<String, String> reconfigurationEventMap = new HashMap<>();
 +        entity.getReconfiguredStaticProperties().forEach(staticProperty -> {
 +            if (staticProperty instanceof FreeTextStaticProperty) {
 +                reconfigurationEventMap.put(staticProperty.getInternalName(),
 +                        ((FreeTextStaticProperty) staticProperty).getValue());
 +            }
 +        });
 +        return toJson(reconfigurationEventMap).getBytes(StandardCharsets.UTF_8);
 +    }
 +
 +    private void updateAndSyncNodeInfoDescription(InvocableRegistration registration) {
 +        setSupportedPipelineElements(registration.getSupportedPipelineElementAppIds());
 +        try {
 +            String url = generateBackendEndpoint();
 +            String desc = toJson(getNodeInfoDescription());
 +            Request.Put(url)
 +                    .bodyString(desc, ContentType.APPLICATION_JSON)
 +                    .execute();
 +        } catch (IOException e) {
 +            e.printStackTrace();
 +        }
 +    }
 +
 +    private List<InvocableStreamPipesEntity> getAllInvocables() {
 +        return RunningInvocableInstances.INSTANCE.getAll();
 +    }
 +
 +    private NodeInfoDescription getNodeInfoDescription() {
 +        return NodeManager.getInstance().retrieveNodeInfoDescription();
 +    }
 +
 +    private void setSupportedPipelineElements(List<String> supportedPipelineElements) {
 +        NodeManager.getInstance()
 +                .retrieveNodeInfoDescription()
 +                .setSupportedElements(supportedPipelineElements);
 +    }
 +
 +    private TransportProtocol getReconfigurationTransportProtocol(InvocableStreamPipesEntity graph) {
 +        TransportProtocol tp = graph.getInputStreams().get(0).getEventGrounding().getTransportProtocol();
 +        tp.setTopicDefinition(makeReconfigurationTopic(graph.getDeploymentRunningInstanceId()));
 +        return tp;
 +    }
 +
 +    private TopicDefinition makeReconfigurationTopic(String runningInstanceId) {
 +        return new SimpleTopicDefinition( RECONFIGURATION_TOPIC + DOT + runningInstanceId);
 +    }
 +
 +    private String generateBackendEndpoint() {
 +        return HTTP_PROTOCOL
 +                + NodeControllerConfig.INSTANCE.backendLocation()
 +                + COLON
 +                + NodeControllerConfig.INSTANCE.backendPort()
 +                + SLASH
 +                + "streampipes-backend/api/v2/users/admin@streampipes.org/nodes"
 +                + SLASH
 +                + NodeControllerConfig.INSTANCE.getNodeControllerId();
 +    }
 +
 +    private void registerAtConsul(InvocableRegistration registration) {
 +        try {
 +            String endpoint = consulURL().toString() + SLASH + CONSUL_REGISTRATION_ROUTE;
 +            Request.Put(endpoint)
 +                    .addHeader("accept", "application/json")
 +                    .body(new StringEntity(toJson(registration.getConsulServiceRegistrationBody())))
 +                    .execute();
 +        } catch (IOException e) {
 +            e.printStackTrace();
 +        }
 +    }
 +
 +    private Response handleResponse(org.apache.http.client.fluent.Response httpResp) throws JsonSyntaxException,
 +            IOException {
 +        String resp = httpResp.returnContent().asString();
 +        return JacksonSerializer
 +                .getObjectMapper()
 +                .readValue(resp, Response.class);
 +    }
 +
 +    private static URL consulURL() {
 +        Map<String, String> env = System.getenv();
 +        URL url = null;
 +
 +        if (env.containsKey(SP_URL)) {
 +            try {
 +                URL coreServerUrl = new URL(env.get(SP_URL));
 +                url = new URL("http", coreServerUrl.getHost(), CONSUL_DEFAULT_PORT, "");
 +            } catch (MalformedURLException e) {
 +                e.printStackTrace();
 +            }
 +        } else if (env.containsKey(CONSUL_LOCATION)) {
 +            try {
 +                url = new URL("http", env.get(CONSUL_LOCATION), CONSUL_DEFAULT_PORT, "");
 +            } catch (MalformedURLException e) {
 +                e.printStackTrace();
 +            }
 +        } else {
 +            try {
 +                url = new URL("http", "localhost", CONSUL_DEFAULT_PORT, "");
 +            } catch (MalformedURLException e) {
 +                e.printStackTrace();
 +            }
 +        }
 +        return url;
 +    }
 +
 +    private <T> String toJson(T element) {
 +        try {
 +            return JacksonSerializer.getObjectMapper().writeValueAsString(element);
 +        } catch (JsonProcessingException e) {
 +            throw new SpRuntimeException("Could not serialize object: " + element, e);
 +        }
 +    }
 +
 +}
diff --cc streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/storage/MapDBImpl.java
index f07302c,0000000..1916534
mode 100644,000000..100644
--- a/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/storage/MapDBImpl.java
+++ b/streampipes-node-controller-container/src/main/java/org/apache/streampipes/node/controller/container/storage/MapDBImpl.java
@@@ -1,80 -1,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.node.controller.container.storage;
 +
 +import org.mapdb.DB;
 +import org.mapdb.DBMaker;
 +import org.mapdb.Serializer;
 +
 +import java.io.File;
 +import java.util.List;
 +import java.util.concurrent.ConcurrentMap;
 +import java.util.stream.Collectors;
 +
 +public class MapDBImpl implements CRUDStorage {
 +
 +    private static final String DB_STORAGE_PATH = "/var/lib/streampipes/";
 +
 +    private DB db;
 +    private ConcurrentMap<String, Object> map;
 +
 +    public MapDBImpl(File dbFile) {
 +        if("true".equals(System.getenv("SP_DEBUG"))) {
++//            db = DBMaker
++//                    .memoryDB()
++//                    .closeOnJvmShutdown()
++//                    .make();
 +            db = DBMaker
-                     .memoryDB()
++                    .fileDB(dbFile)
 +                    .closeOnJvmShutdown()
 +                    .make();
 +        } else {
 +            db = DBMaker
 +                    .fileDB(DB_STORAGE_PATH + dbFile)
 +                    .closeOnJvmShutdown()
 +                    .make();
 +        }
 +        map = db.hashMap("nodectlcache", Serializer.STRING, Serializer.JAVA)
 +                .createOrOpen();
 +    }
 +
 +    @Override
 +    public <T> void create(String id, T value) {
 +        map.put(id, value);
 +    }
 +
 +    @Override
 +    public <T> T retrieve(String id) {
 +        return (T) map.get(id);
 +    }
 +
 +    @Override
 +    public <T> List<T> retrieveAll() {
 +        return map.values().stream().map(v -> (T) v).collect(Collectors.toList());
 +    }
 +
 +    @Override
 +    public <T> void update(String id, T value) {
 +        map.put(id, value);
 +    }
 +
 +    @Override
 +    public void delete(String id) {
 +        map.remove(id);
 +    }
 +
 +    public void close() {
 +        db.close();
 +    }
 +}
diff --cc ui/package.json
index 29ff920,efc485d..704e013
--- a/ui/package.json
+++ b/ui/package.json
@@@ -65,7 -65,7 +65,8 @@@
      "konva": "3.2.4",
      "leaflet": "1.6.0",
      "lodash": "4.17.20",
 +    "material-design-icons": "^3.0.1",
+     "material-icons": "^0.5.3",
      "ng-file-upload": "9.0.13",
      "ng-pick-datetime": "7.0.0",
      "ng-prettyjson": "0.1.8",
diff --cc ui/src/app/configuration/node-configuration/node-configuration.component.scss
index 4e4fe65,0000000..ad31613
mode 100644,000000..100644
--- a/ui/src/app/configuration/node-configuration/node-configuration.component.scss
+++ b/ui/src/app/configuration/node-configuration/node-configuration.component.scss
@@@ -1,166 -1,0 +1,167 @@@
 +/*
 + * 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 'src/scss/sp/colors';
 +@import url("https://fonts.googleapis.com/icon?family=Material+Icons");
 +
 +.page-container-padding-inner {
 +    padding: 10px;
 +}
 +
 +.form-width {
 +    width:80%;
 +}
 +
 +.node-card {
 +    max-width: 450px;
 +}
 +
 +mat-card {
 +    box-shadow: 0 2px 1px -1px rgba(0, 0, 0, .2), 0 1px 1px 0 rgba(0, 0, 0, .14), 0 1px 3px 0 rgba(0, 0, 0, .12);
 +    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;
 +}
 +
 +.div-node-tag{
 +     display: inline-block;
 +     margin-bottom: 10px;
 + }
 +
 +.span-node-tag{
 +    font-size: 12px;
 +    font-weight: bold;
 +    color: whitesmoke;
 +    background: dimgray;
 +    padding: 5px;
 +}
 +
 +.div-sa-tag{
 +    display: inline-block;
 +    margin-bottom: 10px;
 +}
 +
 +.span-sa-tag{
 +    font-size: 12px;
 +    font-weight: bold;
 +    color: whitesmoke;
 +    background: lightslategray;
 +    padding: 5px;
 +}
 +
 +.node-header-avatar {
 +    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;*/
 +/*    width: 100%;*/
 +/*    padding: 3px 0px;*/
 +/*    margin-bottom: 1px;*/
 +/*    text-align:center;*/
 +/*}*/
 +
 +.ft-foot {
 +    column-count: 3;
 +    float: left;
 +    width: 100%;
 +    padding: 3px 0px;
 +    margin-bottom: 1px;
 +    text-align:center;
++    list-style: none;
 +}
 +.ft-foot .ft-foot-4 ul li {
 +    display: inline-block;
 +    margin:0 2px;
 +}
 +.inclusion {
 +    color: darkgrey;
 +    padding: 5px 0 5px 0;
 +    /*display: block;*/
 +    letter-spacing: 5px;
 +    text-align: center;
 +}
 +.inclusion span {
 +    display: block;
 +    font-size: 10px;
 +    text-transform: uppercase;
 +}
 +
 +hr {
 +    display: block;
 +    height: 1px;
 +    border: 0;
 +    border-top: 1px solid #ccc;
 +    margin: 0.6em 3em;
 +    padding: 0;
 +}
 +
 +.fa-custom {
 +    font-size: 2em;
 +}
diff --cc ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
index f8ac965,385d5c3..6cfd193
--- 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,7 -40,10 +40,11 @@@ import {ConfirmDialogComponent} from ".
  import {MatDialog} from "@angular/material/dialog";
  import {EditorService} from "../../services/editor.service";
  import {PipelineService} from "../../../platform-services/apis/pipeline.service";
 +import {pipe} from "rxjs";
+ import {PipelineCanvasScrollingService} from "../../services/pipeline-canvas-scrolling.service";
+ import {JsplumbFactoryService} from "../../services/jsplumb-factory.service";
+ import Panzoom, {PanzoomObject} from "@panzoom/panzoom";
+ import {PipelineElementDraggedService} from "../../services/pipeline-element-dragged.service";
  
  
  @Component({
@@@ -60,6 -62,21 +63,22 @@@ export class PipelineAssemblyComponent 
      @Input()
      allElements: PipelineElementUnion[];
  
+     @Output()
+     pipelineCanvasMaximizedEmitter: EventEmitter<boolean> = new EventEmitter<boolean>();
+ 
+     JsplumbBridge: JsplumbBridge;
+ 
+     pipelineCanvasMaximized: boolean = false;
+ 
+     currentMouseOverElement: any;
+     currentZoomLevel: any;
+     preview: any;
+ 
+     selectMode: any;
+     currentPipelineName: any;
+     currentPipelineDescription: any;
++    currentEventRelayStrategy: any;
+ 
      errorMessagesDisplayed: any = false;
  
      pipelineValid: boolean = false;
diff --cc ui/src/app/editor/editor.module.ts
index f681b5b,b5c10e0..71e291d
--- a/ui/src/app/editor/editor.module.ts
+++ b/ui/src/app/editor/editor.module.ts
@@@ -59,9 -59,12 +59,14 @@@ import {CustomOutputStrategyComponent} 
  import {PropertySelectionComponent} from "./components/output-strategy/property-selection/property-selection.component";
  import {UserDefinedOutputStrategyComponent} from "./components/output-strategy/user-defined-output/user-defined-output.component";
  import {ConnectModule} from "../connect/connect.module";
 +import {MatSliderModule} from "@angular/material/slider";
 +import { MigratePipelineProcessorsComponent } from './dialog/migrate-pipeline-processors/migrate-pipeline-processors.component';
  import {PipelineElementTemplateConfigComponent} from "./components/pipeline-element-template-config/pipeline-element-template-config.component";
+ import {EnabledPipelineElementFilter} from "./filter/enabled-pipeline-element.filter";
+ import {PipelineElementDraggedService} from "./services/pipeline-element-dragged.service";
+ import {PipelineCanvasScrollingService} from "./services/pipeline-canvas-scrolling.service";
+ import {JsplumbEndpointService} from "./services/jsplumb-endpoint.service";
+ import {JsplumbFactoryService} from "./services/jsplumb-factory.service";
  
  @NgModule({
      imports: [
@@@ -79,7 -82,6 +84,8 @@@
          MatProgressSpinnerModule,
          ShowdownModule,
          ReactiveFormsModule,
-         MatSliderModule
++        MatSliderModule,
++        ReactiveFormsModule,
      ],
      declarations: [
          CompatibleElementsComponent,