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 88e204d140f11b6835a101b904ff1d9b79044922
Author: Patrick Wiener <[email protected]>
AuthorDate: Fri Apr 2 15:02:07 2021 +0200

    [WIP] add new node to cluster
---
 .../aarch64.Dockerfile => build-docker.sh          |  28 ++--
 streampipes-node-controller/aarch64.Dockerfile     |   8 +-
 .../orchestrator/docker/utils/DockerUtils.java     |   1 +
 .../controller/container/storage/MapDBImpl.java    |   4 +
 .../StreamPipesExternalDataProcessor.java          |   3 +-
 ui/src/app/configuration/configuration.module.ts   |  11 +-
 .../node-add-details.component.html                | 177 ++++++++++++++++++++
 .../node-add-details.component.scss                | 126 ++++++++++++++
 .../node-add-details/node-add-details.component.ts | 183 +++++++++++++++++++++
 .../node-configuration.component.html              | 110 ++++++++-----
 .../node-configuration.component.scss              |  22 ++-
 .../node-configuration.component.ts                |  21 ++-
 .../dashboard/registry/widget-config-builder.ts    |   4 +-
 .../node-tag-selector.component.html               |  29 ++++
 .../node-tag-selector.component.scss               |  40 +++++
 .../node-tag-selector.component.ts                 |  79 +++++++++
 .../save-pipeline/save-pipeline.component.html     |  24 ++-
 .../save-pipeline/save-pipeline.component.ts       |  77 +++++++--
 ui/src/app/editor/editor.module.ts                 |   6 +-
 19 files changed, 861 insertions(+), 92 deletions(-)

diff --git a/streampipes-node-controller/aarch64.Dockerfile b/build-docker.sh
old mode 100644
new mode 100755
similarity index 62%
copy from streampipes-node-controller/aarch64.Dockerfile
copy to build-docker.sh
index b8c87b0..41ba9fd
--- a/streampipes-node-controller/aarch64.Dockerfile
+++ b/build-docker.sh
@@ -1,3 +1,4 @@
+#!/bin/sh
 # 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.
@@ -13,21 +14,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-ARG BASE_IMAGE=arm64v8/openjdk:11-jre-slim
-FROM $BASE_IMAGE
+#!/usr/bin/env bash
 
-MAINTAINER [email protected]
+repo=apachestreampipes
+version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
 
-EXPOSE 7077
-ENV CONSUL_LOCATION consul
+docker_build(){
+  docker build --no-cache --pull \
+  -t $repo/$1:$version \
+  -f $2/Dockerfile $2
+}
 
-COPY qemu-aarch64-static /usr/bin
-RUN set -ex; \
-    apt -y update; \
-    apt -y --no-install-recommends install curl; \
-    apt clean; \
-    rm -rf /tmp/apache-* /var/lib/apt/lists/*
-
-COPY target/streampipes-node-controller.jar  /streampipes-node-controller.jar
-
-ENTRYPOINT ["java", "-jar", "/streampipes-node-controller.jar"]
+echo "Start Docker builds ..."
+docker_build backend streampipes-backend
+docker_build node-controller streampipes-node-controller
+docker_build ui ui
\ No newline at end of file
diff --git a/streampipes-node-controller/aarch64.Dockerfile 
b/streampipes-node-controller/aarch64.Dockerfile
index b8c87b0..856f64a 100644
--- a/streampipes-node-controller/aarch64.Dockerfile
+++ b/streampipes-node-controller/aarch64.Dockerfile
@@ -14,14 +14,18 @@
 # limitations under the License.
 
 ARG BASE_IMAGE=arm64v8/openjdk:11-jre-slim
-FROM $BASE_IMAGE
 
+FROM arm64v8/ubuntu:18.04 as build-dev
+RUN apt -y update; \
+    apt -y --no-install-recommends install qemu-user-static
+
+FROM $BASE_IMAGE
 MAINTAINER [email protected]
 
 EXPOSE 7077
 ENV CONSUL_LOCATION consul
 
-COPY qemu-aarch64-static /usr/bin
+COPY --from=build-dev /usr/bin/qemu-aarch64-static /usr/bin
 RUN set -ex; \
     apt -y update; \
     apt -y --no-install-recommends install curl; \
diff --git 
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/container/management/orchestrator/docker/utils/DockerUtils.java
 
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/container/management/orchestrator/docker/utils/DockerUtils.java
index 4bad83f..5ee6a2b 100644
--- 
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/container/management/orchestrator/docker/utils/DockerUtils.java
+++ 
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/container/management/orchestrator/docker/utils/DockerUtils.java
@@ -183,6 +183,7 @@ public class DockerUtils {
                 .portBindings(portBindings)
                 .networkMode(network)
                 .restartPolicy(HostConfig.RestartPolicy.unlessStopped())
+                //.extraHosts()
                 .build();
     }
 
diff --git 
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/container/storage/MapDBImpl.java
 
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/container/storage/MapDBImpl.java
index f07302c..6824c87 100644
--- 
a/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/container/storage/MapDBImpl.java
+++ 
b/streampipes-node-controller/src/main/java/org/apache/streampipes/node/controller/container/storage/MapDBImpl.java
@@ -42,6 +42,7 @@ public class MapDBImpl implements CRUDStorage {
         } else {
             db = DBMaker
                     .fileDB(DB_STORAGE_PATH + dbFile)
+                    .transactionEnable()
                     .closeOnJvmShutdown()
                     .make();
         }
@@ -52,6 +53,7 @@ public class MapDBImpl implements CRUDStorage {
     @Override
     public <T> void create(String id, T value) {
         map.put(id, value);
+        db.commit();
     }
 
     @Override
@@ -67,11 +69,13 @@ public class MapDBImpl implements CRUDStorage {
     @Override
     public <T> void update(String id, T value) {
         map.put(id, value);
+        db.commit();
     }
 
     @Override
     public void delete(String id) {
         map.remove(id);
+        db.commit();
     }
 
     public void close() {
diff --git 
a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/StreamPipesExternalDataProcessor.java
 
b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/StreamPipesExternalDataProcessor.java
index b3c73c2..1992905 100644
--- 
a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/StreamPipesExternalDataProcessor.java
+++ 
b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/StreamPipesExternalDataProcessor.java
@@ -36,6 +36,7 @@ public abstract class StreamPipesExternalDataProcessor 
extends StandaloneExterna
         implements ExternalEventProcessor<ProcessorParams> {
 
     // endpoint of Python processor runs here
+    private static final String HTTP_PROTOCOL = "http://";;
     private static final String PYTHON_ENDPOINT = "localhost:5000";
 
     private String invocationId;
@@ -88,7 +89,7 @@ public abstract class StreamPipesExternalDataProcessor 
extends StandaloneExterna
         String responseString = null;
 
         try {
-            responseString = Request.Post(PYTHON_ENDPOINT + "/" + endpoint)
+            responseString = Request.Post(HTTP_PROTOCOL + PYTHON_ENDPOINT + 
"/" + endpoint)
                     .bodyString(payload, ContentType.APPLICATION_JSON)
                     .connectTimeout(1000)
                     .socketTimeout(100000)
diff --git a/ui/src/app/configuration/configuration.module.ts 
b/ui/src/app/configuration/configuration.module.ts
index 373fd05..ec904ca 100644
--- a/ui/src/app/configuration/configuration.module.ts
+++ b/ui/src/app/configuration/configuration.module.ts
@@ -25,7 +25,7 @@ import { MatInputModule } from '@angular/material/input';
 import { MatTooltipModule } from '@angular/material/tooltip';
 import { FlexLayoutModule } from '@angular/flex-layout';
 import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 
 import { ConfigurationComponent } from './configuration.component';
 import { ConfigurationService } from './shared/configuration.service';
@@ -44,6 +44,8 @@ import { DatalakeRestService } from 
'../core-services/datalake/datalake-rest.ser
 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";
+import { NodeAddDetailsComponent } from 
'./node-configuration/node-add-details/node-add-details.component';
+import {MatDatepickerModule} from "@angular/material/datepicker";
 
 @NgModule({
     imports: [
@@ -58,7 +60,9 @@ import {MatChipsModule} from "@angular/material/chips";
         MatTooltipModule,
         FormsModule,
         DragDropModule,
-        MatChipsModule
+        MatChipsModule,
+        ReactiveFormsModule,
+        MatDatepickerModule
     ],
     declarations: [
         ConfigurationComponent,
@@ -73,7 +77,8 @@ import {MatChipsModule} from "@angular/material/chips";
         NodeConfigurationComponent,
         MessagingConfigurationComponent,
         DatalakeConfigurationComponent,
-        NodeConfigurationDetailsComponent
+        NodeConfigurationDetailsComponent,
+        NodeAddDetailsComponent
     ],
     providers: [
       ConfigurationService,
diff --git 
a/ui/src/app/configuration/node-configuration/node-add-details/node-add-details.component.html
 
b/ui/src/app/configuration/node-configuration/node-add-details/node-add-details.component.html
new file mode 100644
index 0000000..c05a54d
--- /dev/null
+++ 
b/ui/src/app/configuration/node-configuration/node-add-details/node-add-details.component.html
@@ -0,0 +1,177 @@
+<!--
+  ~ 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">
+
+            <form [formGroup]="nodeMetadataFormGroup">
+                <div fxFlex="100" fxLayout="column">
+                    <mat-form-field>
+                        <mat-label>Backend host (IP/DNS)</mat-label>
+                        <input [formControlName]="'backendHost'" matInput 
required>
+                    </mat-form-field>
+
+                    <mat-form-field>
+                        <mat-label>Node host (IP/DNS)</mat-label>
+                        <input [formControlName]="'nodeHost'" matInput 
required>
+                    </mat-form-field>
+
+                    <mat-form-field>
+                        <mat-label>API token</mat-label>
+                        <input [formControlName]="'token'" matInput required>
+                    </mat-form-field>
+                </div>
+            </form>
+
+            <mat-form-field class="example-chip-list">
+                <mat-label>Node tags</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>
+
+            <mat-slide-toggle color="primary" [(ngModel)]="advancedSettings">
+                Configure advanced options
+            </mat-slide-toggle>
+
+            <div style="margin-top: 1em">
+                <mat-accordion class="example-headers-align">
+                    <mat-expansion-panel [expanded]="advancedSettings">
+                        <mat-expansion-panel-header>
+                            <mat-panel-title>
+                                Settings
+                            </mat-panel-title>
+                            <mat-panel-description>
+                                Configure advanced options
+                            </mat-panel-description>
+                        </mat-expansion-panel-header>
+
+                        <div>
+                            <div fxFlex="100" fxLayout="row">
+                                <div fxFlex="50" fxLayout="row" 
fxLayoutAlign="left center">
+                                    <span>Storage path</span>
+                                </div>
+                            </div>
+                            <div fxFlex="50" fxLayout="row" 
fxLayoutAlign="start center">
+                                <mat-form-field>
+                                    <mat-label></mat-label>
+                                    <input [disabled]="!advancedSettings" 
matInput [value]="storagePath">
+                                </mat-form-field>
+                            </div>
+                        </div>
+
+                        <div>
+                            <div fxFlex="100" fxLayout="row">
+                                <div fxFlex="50" fxLayout="row" 
fxLayoutAlign="left center">
+                                    <span>Docker pruning interval (sec)</span>
+                                </div>
+                            </div>
+                            <div fxFlex="50" fxLayout="row" 
fxLayoutAlign="start center">
+                                <mat-form-field>
+                                    <mat-label></mat-label>
+                                    <input [disabled]="!advancedSettings" 
matInput [value]="dockerNodePruningFreq">
+                                </mat-form-field>
+                            </div>
+                        </div>
+
+                        <div>
+                            <div fxFlex="100" fxLayout="row">
+                                <div fxFlex="50" fxLayout="row" 
fxLayoutAlign="left center">
+                                    <span>Resource update interval (sec)</span>
+                                </div>
+                            </div>
+                            <div fxFlex="50" fxLayout="row" 
fxLayoutAlign="start center">
+                                <mat-form-field>
+                                    <mat-label></mat-label>
+                                    <input 
[disabled]="!advancedSettings"matInput [value]="resourceUpdateFreq">
+                                </mat-form-field>
+                            </div>
+                        </div>
+
+                        <div>
+                            <div fxFlex="100" fxLayout="row">
+                                <div fxFlex="50" fxLayout="row" 
fxLayoutAlign="left center">
+                                    <span>Event buffer size (#events)</span>
+                                </div>
+                            </div>
+                            <div fxFlex="50" fxLayout="row" 
fxLayoutAlign="start center">
+                                <mat-form-field>
+                                    <mat-label></mat-label>
+                                    <input [disabled]="!advancedSettings" 
matInput [value]="eventBufferSize">
+                                </mat-form-field>
+                            </div>
+                        </div>
+                    </mat-expansion-panel>
+                </mat-accordion>
+
+                    <mat-vertical-stepper *ngIf="dockerCommandCreated" 
#stepper>
+                        <mat-step [stepControl]="firstFormGroup">
+                            <form [formGroup]="firstFormGroup">
+                                <ng-template matStepLabel>Create docker 
network</ng-template>
+                                <div class="flex-container">
+                                    <div *ngIf="dockerCommandCreated" 
fxFlex="100" fxLayout="row" fxLayoutAlign="left center"
+                                         class="docker-command-container">
+                                        docker network create spnet
+                                    </div>
+                                </div>
+                                <div>
+                                    <button mat-button 
matStepperNext>Next</button>
+                                </div>
+                            </form>
+                        </mat-step>
+                        <mat-step [stepControl]="secondFormGroup">
+                            <form [formGroup]="secondFormGroup">
+                                <ng-template matStepLabel>Start node 
controller container</ng-template>
+                                <div class="flex-container">
+                                    <div *ngIf="dockerCommandCreated" 
fxFlex="100" fxLayout="row" fxLayoutAlign="left center"
+                                         class="docker-command-container">
+                                        {{dockerRunCommand}}
+                                    </div>
+                                </div>
+                                <div>
+                                    <button mat-button 
(click)="hide()">Done</button>
+                                </div>
+                            </form>
+                        </mat-step>
+                    </mat-vertical-stepper>
+
+
+            </div>
+        </div>
+    </div>
+    <mat-divider></mat-divider>
+    <div class="sp-dialog-actions" fxLayoutAlign="left center">
+        <button [disabled]="!nodeMetadataFormGroup.valid || added" mat-button 
mat-raised-button
+                color="primary" (click)="createDocker()" 
style="margin-right:10px;">
+            Create Docker Command
+        </button>
+        <button mat-button mat-raised-button class="mat-basic" 
(click)="hide()">
+            {{added ? 'Close' : 'Cancel'}}
+        </button>
+    </div>
+</div>
diff --git 
a/ui/src/app/configuration/node-configuration/node-add-details/node-add-details.component.scss
 
b/ui/src/app/configuration/node-configuration/node-add-details/node-add-details.component.scss
new file mode 100644
index 0000000..77add90
--- /dev/null
+++ 
b/ui/src/app/configuration/node-configuration/node-add-details/node-add-details.component.scss
@@ -0,0 +1,126 @@
+/*
+ * 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;
+  }
+}
+
+.flex-container {
+  margin-top: 1em;
+  width: 100%
+}
+
+.new-docker-command {
+  width: 200px;
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+
+.docker-tag {
+  margin-right: 10px;
+  font-weight: bold;
+  padding: 10px;
+}
+
+.docker-command-container {
+  background: #f6f6f6;
+  border: 1px solid #cccccc;
+  padding: 10px;
+  color: black;
+  //margin-left: 10px;
+  //margin-right: 10px;
+  overflow-x: auto;
+  white-space: nowrap;
+}
diff --git 
a/ui/src/app/configuration/node-configuration/node-add-details/node-add-details.component.ts
 
b/ui/src/app/configuration/node-configuration/node-add-details/node-add-details.component.ts
new file mode 100644
index 0000000..b41bd7b
--- /dev/null
+++ 
b/ui/src/app/configuration/node-configuration/node-add-details/node-add-details.component.ts
@@ -0,0 +1,183 @@
+/*
+ * 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 } from '@angular/core';
+import {NodeService} from "../../../platform-services/apis/node.service";
+import {DialogRef} from "../../../core-ui/dialog/base-dialog/dialog-ref";
+import {MatSnackBar} from "@angular/material/snack-bar";
+import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";
+import {STEPPER_GLOBAL_OPTIONS} from "@angular/cdk/stepper";
+import {COMMA, ENTER} from "@angular/cdk/keycodes";
+import {MatChipInputEvent} from "@angular/material/chips";
+import {VersionInfo} from "../../../info/versions/service/version-info.model";
+import {RestApi} from "../../../services/rest-api.service";
+
+@Component({
+  selector: 'node-add-details',
+  templateUrl: './node-add-details.component.html',
+  styleUrls: ['./node-add-details.component.scss'],
+  // providers: [{
+  //   provide: STEPPER_GLOBAL_OPTIONS, useValue: {showError: true}
+  // }]
+})
+export class NodeAddDetailsComponent implements OnInit {
+
+  nodeMetadataFormGroup: FormGroup = new FormGroup({});
+
+  added: boolean = false;
+  advancedSettings: boolean;
+  dockerCommandCreated: boolean;
+  errorMessage: string = '';
+
+  selectable = true;
+  visible = true;
+  removable = true;
+  addOnBlur = true;
+  readonly separatorKeysCodes: number[] = [ENTER, COMMA];
+  tmpTags: string[];
+
+  versionInfo: VersionInfo;
+  backend: string = '';
+  nodeIp: string = '';
+  apiToken: string = '';
+  storagePath: string = '/var/lib/streampipes';
+  resourceUpdateFreq: string = '30';
+  dockerNodePruningFreq: string = '3600';
+  eventBufferSize: string = '1000';
+
+  dockerRunCommand: string;
+
+  firstFormGroup: FormGroup;
+  secondFormGroup: FormGroup;
+
+
+  constructor(private nodeService: NodeService,
+              private dialogRef: DialogRef<NodeAddDetailsComponent>,
+              private restApi: RestApi,
+              private _snackBar: MatSnackBar,
+              private _formBuilder: FormBuilder) { }
+
+  ngOnInit(): void {
+    this.dockerCommandCreated = false;
+    this.advancedSettings = false;
+    this.tmpTags = []
+    this.getVersion();
+
+    this.nodeMetadataFormGroup.addControl("backendHost", new 
FormControl(this.backend,
+        [Validators.required,
+          Validators.maxLength(40)]))
+
+    this.nodeMetadataFormGroup.addControl("nodeHost", new 
FormControl(this.nodeIp,
+        [Validators.required,
+          Validators.maxLength(15)]))
+    this.nodeMetadataFormGroup.addControl("token", new 
FormControl(this.apiToken,
+        [Validators.maxLength(80)]))
+
+
+    
this.nodeMetadataFormGroup.controls["backendHost"].valueChanges.subscribe(value 
=> {
+      this.backend = value;
+    });
+
+    
this.nodeMetadataFormGroup.controls["nodeHost"].valueChanges.subscribe(value => 
{
+      this.nodeIp = value;
+    });
+
+    this.nodeMetadataFormGroup.controls["token"].valueChanges.subscribe(value 
=> {
+      this.apiToken = value;
+    });
+
+    this.firstFormGroup = this._formBuilder.group({
+      firstCtrl: ['', Validators.required]
+    });
+    this.secondFormGroup = this._formBuilder.group({
+      secondCtrl: ['', Validators.required]
+    });
+
+  }
+
+  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);
+    }
+  }
+
+  openSnackBar(message: string) {
+    this._snackBar.open(message, "close", {
+      duration: 3000,
+    });
+  }
+
+  createDocker() {
+    // add new node
+    this.buildDockerCommand();
+    this.dockerCommandCreated = true;
+  }
+
+  buildDockerCommand() {
+    this.dockerRunCommand = 'docker run -d -p 7077:7077 ';
+    this.dockerRunCommand += '--restart always ';
+    this.dockerRunCommand += '--hostname streampipes-node-controller '
+    this.dockerRunCommand += '--name streampipes-node-controller ';
+    this.dockerRunCommand += `-e SP_URL=http://${this.backend}:8030 `;
+    this.dockerRunCommand += `-e 
SP_NODE_CONTROLLER_URL=http://${this.nodeIp}:7077 `;
+    this.dockerRunCommand += `-e SP_API_KEY=${this.apiToken} `;
+    if (this.tmpTags.length > 0) {
+      this.dockerRunCommand += `-e 
SP_NODE_LOCATION="${this.tagsToString(this.tmpTags)}" `;
+    }
+    if (this.advancedSettings) {
+      this.dockerRunCommand += `-e 
SP_DOCKER_PRUNING_FREQ_SECS=${this.dockerNodePruningFreq} `;
+      this.dockerRunCommand += `-e 
SP_NODE_RESOURCE_UPDATE_FREQ_SECS=${this.resourceUpdateFreq} `;
+      this.dockerRunCommand += `-e 
SP_NODE_EVENT_BUFFER_SIZE=${this.eventBufferSize} `;
+    }
+    this.dockerRunCommand += '-v /var/run/docker.sock:/var/run/docker.sock ';
+    this.dockerRunCommand += '-v /var/lib/streampipes:/var/lib/streampipes ';
+    this.dockerRunCommand += 
`apachestreampipes/node-controller:${this.versionInfo.backendVersion}`;
+
+  }
+
+  tagsToString(tags: string[]){
+    return tags.join(';')
+  }
+
+  getVersion(){
+    this.restApi.getVersionInfo().subscribe((response) => {
+      this.versionInfo = response as VersionInfo;
+    })
+  }
+
+  copyDockerCmd() {
+
+  }
+}
diff --git 
a/ui/src/app/configuration/node-configuration/node-configuration.component.html 
b/ui/src/app/configuration/node-configuration/node-configuration.component.html
index e339f40..e94dcf2 100644
--- 
a/ui/src/app/configuration/node-configuration/node-configuration.component.html
+++ 
b/ui/src/app/configuration/node-configuration/node-configuration.component.html
@@ -25,6 +25,15 @@
             </div>
         </div>
         <div fxFlex="100" fxLayoutAlign="start start" class="sp-blue-border 
page-container-padding-inner">
+            <div fxLayoutAlign="center right">
+                <button class="md-raised md-primary submit-button"
+                        mat-fab color="accent"
+                        type="submit"
+                        matTooltip="add new node"
+                        (click)="addNode()">
+                    <mat-icon>add</mat-icon>
+                </button>
+            </div>
             <mat-grid-list
                     [cols]="3" [rowHeight]="300"
                     [gutterSize]="10" fxFlex="100">
@@ -32,47 +41,56 @@
                 <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>
+                            <div class="div-menu">
+                                <button mat-icon-button 
[matMenuTriggerFor]="menu" aria-label="node menu">
+                                    <mat-icon>more_vert</mat-icon>
                                 </button>
+                                <mat-menu #menu="matMenu" style="font-size: 
10pt">
+                                    <button mat-menu-item
+                                            *ngIf="!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>
+                                        <span>activate</span>
+                                    </button>
+                                    <button mat-menu-item
+                                            *ngIf="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>
+                                        <span>deactivate</span>
+                                    </button>
+<!--                                    <button mat-menu-item-->
+<!--                                            [disabled]="node.active"-->
+<!--                                            matTooltip="{{!node.active ? 
'evict processors' :-->
+<!--                                            'evict processors option not 
available'}}"-->
+<!--                                            (click)="evict(node)">-->
+<!--                                        <mat-icon-->
+<!--                                                [ngClass]="node.active ? 
'node-inactive' : 'node-evict'"-->
+<!--                                                
matTooltipPosition="above">-->
+<!--                                            
swap_horizontal_circle_filled-->
+<!--                                        </mat-icon>-->
+<!--                                        <span>evict</span>-->
+<!--                                    </button>-->
+                                    <button mat-menu-item
+                                            matTooltip="Edit node 
configuration"
+                                            matTooltipPosition="above"
+                                            (click)="settings(node)">
+                                        <mat-icon
+                                                [ngClass]="'node-inactive'">
+                                            sell
+                                        </mat-icon>
+                                        <span>tags</span>
+                                    </button>
+                                </mat-menu>
                             </div>
                             <div mat-card-avatar class="node-header-avatar">
                                 <button mat-icon-button 
class="node-mat-icon-button" disabled>
@@ -87,6 +105,8 @@
                                 {{node.nodeControllerId}} | 
<b>{{node.nodeResources.softwareResource.os}}</b>
                                 <div style="color: lightgrey;">
                                     last contact: {{currentTimestamp}}
+                                    <span *ngIf="node.active" 
class="span-node-active">active</span>
+                                    <span *ngIf="!node.active" 
class="span-node-inactive">inactive</span>
                                 </div>
                             </mat-card-subtitle>
                         </mat-card-header>
@@ -124,6 +144,16 @@
 <!--                            </div>-->
                         </mat-card-content>
                         <mat-card-actions>
+<!--                            <mat-divider></mat-divider>-->
+<!--                            <div style="margin-top: 1em" fxLayout="row"-->
+<!--                                 fxLayoutAlign="start center" 
fxFlex="100">-->
+<!--                                <div style="margin: auto; border: 1px 
solid  rgb(57, 181, 74); color: rgb(57, 181, 74)">-->
+<!--                                    <button mat-button>-->
+<!--                                        
<mat-icon>add_circle_outline</mat-icon>-->
+<!--                                        add container-->
+<!--                                    </button>-->
+<!--                                </div>-->
+<!--                            </div>-->
                         </mat-card-actions>
                     </mat-card>
                 </mat-grid-tile>
diff --git 
a/ui/src/app/configuration/node-configuration/node-configuration.component.scss 
b/ui/src/app/configuration/node-configuration/node-configuration.component.scss
index ad31613..4c0a4ff 100644
--- 
a/ui/src/app/configuration/node-configuration/node-configuration.component.scss
+++ 
b/ui/src/app/configuration/node-configuration/node-configuration.component.scss
@@ -69,6 +69,24 @@ li {
     padding: 5px;
 }
 
+.span-node-active{
+    font-size: 12px;
+    font-weight: bold;
+    color: white;
+    background: $sp-color-primary;
+    border: 1px solid $sp-color-primary;
+    padding: 2px;
+}
+
+.span-node-inactive{
+    font-size: 12px;
+    font-weight: bold;
+    color: #f1c40f;
+    background: white;
+    border: 1px solid #f1c40f;
+    padding: 2px;
+}
+
 .div-sa-tag{
     display: inline-block;
     margin-bottom: 10px;
@@ -87,14 +105,14 @@ li {
     padding-top: 6px;
 }
 
-.div-icon-node-actions {
+.div-menu {
     margin-left: auto;
     margin-right: 0;
 }
 
 .node-state {
     //float:right;
-    transform: scale(1.3);
+    transform: scale(1);
 }
 
 .node-activated {
diff --git 
a/ui/src/app/configuration/node-configuration/node-configuration.component.ts 
b/ui/src/app/configuration/node-configuration/node-configuration.component.ts
index 46cd508..0e0369e 100644
--- 
a/ui/src/app/configuration/node-configuration/node-configuration.component.ts
+++ 
b/ui/src/app/configuration/node-configuration/node-configuration.component.ts
@@ -18,9 +18,10 @@
 
 import {Component, OnInit, ViewEncapsulation} from "@angular/core";
 import {
+    ContainerRuntime, ContainerRuntimeUnion, DockerContainerRuntime,
     FieldDeviceAccessResource,
     NodeInfoDescription,
-    NvidiaContainerRuntime
+    NvidiaContainerRuntime, UnnamedStreamPipesEntity
 } from "../../core-model/gen/streampipes-model";
 import {MatSnackBar} from "@angular/material/snack-bar";
 import {zip} from "rxjs";
@@ -30,6 +31,7 @@ import {DataMarketplaceService} from 
"../../connect/services/data-marketplace.se
 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";
+import {NodeAddDetailsComponent} from 
"./node-add-details/node-add-details.component";
 
 @Component({
     selector: 'node-configuration',
@@ -92,11 +94,12 @@ export class NodeConfigurationComponent implements OnInit{
     };
 
     nvidiaRuntime(node: NodeInfoDescription) {
-        let nvidiaRuntime = false;
-        if (node.nodeResources.softwareResource.containerRuntime instanceof 
NvidiaContainerRuntime) {
-            nvidiaRuntime = true;
+        const containerRuntime = 
node.nodeResources.softwareResource.containerRuntime;
+        if(containerRuntime['@class'] === 
"org.apache.streampipes.model.node.resources.software.NvidiaContainerRuntime"){
+            return true
+        } else {
+            return false
         }
-        return nvidiaRuntime;
     }
 
     async changeNodeState(node: NodeInfoDescription, desiredState: boolean) {
@@ -175,6 +178,14 @@ export class NodeConfigurationComponent implements OnInit{
         });
     }
 
+    addNode() {
+        this.DialogService.open(NodeAddDetailsComponent,{
+            panelType: PanelType.SLIDE_IN_PANEL,
+            title: "Add new node",
+            data: {}
+        });
+    }
+
     getDate() {
         this.currentTimestamp = new Date().toLocaleTimeString(['en-US'],
             { hour: '2-digit', minute: "2-digit", second: "2-digit" });
diff --git a/ui/src/app/dashboard/registry/widget-config-builder.ts 
b/ui/src/app/dashboard/registry/widget-config-builder.ts
index e5136ed..da0332e 100644
--- a/ui/src/app/dashboard/registry/widget-config-builder.ts
+++ b/ui/src/app/dashboard/registry/widget-config-builder.ts
@@ -45,9 +45,9 @@ export class WidgetConfigBuilder {
         }
         if (withColors) {
         this.requiredColorParameter(WidgetConfigBuilder.BACKGROUND_COLOR_KEY, 
"Background color", "The background" +
-            " color", "#1B1464");
+            " color", "#F6F6F6");
         
this.requiredColorParameter(WidgetConfigBuilder.PRIMARY_TEXT_COLOR_KEY, 
"Primary text color", "The" +
-                " primary text color", "#FFFFFF");
+                " primary text color", "#333");
         
this.requiredColorParameter(WidgetConfigBuilder.SECONDARY_TEXT_COLOR_KEY, 
"Secondary text color", "The" +
             " secondary text" +
             " color", "#39B54A");
diff --git 
a/ui/src/app/editor/dialog/save-pipeline/node-tag-selector/node-tag-selector.component.html
 
b/ui/src/app/editor/dialog/save-pipeline/node-tag-selector/node-tag-selector.component.html
new file mode 100644
index 0000000..38083b2
--- /dev/null
+++ 
b/ui/src/app/editor/dialog/save-pipeline/node-tag-selector/node-tag-selector.component.html
@@ -0,0 +1,29 @@
+<!--
+  ~ 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 style="margin-top: 1em; margin-bottom: 1em">
+    <mat-chip-list #chipList multiple="true" class="my-chip">
+        <mat-chip *ngFor="let tag of nodeTags"
+                  [value]="tag.name"
+                  [selected]="tag.selected"
+                  (click)="onSelectTag(tag)">
+            {{tag.name}}
+        </mat-chip>
+    </mat-chip-list>
+    <div class="hint-label">Filter nodes by tags</div>
+</div>
\ No newline at end of file
diff --git 
a/ui/src/app/editor/dialog/save-pipeline/node-tag-selector/node-tag-selector.component.scss
 
b/ui/src/app/editor/dialog/save-pipeline/node-tag-selector/node-tag-selector.component.scss
new file mode 100644
index 0000000..0b92957
--- /dev/null
+++ 
b/ui/src/app/editor/dialog/save-pipeline/node-tag-selector/node-tag-selector.component.scss
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *
+ */
+
+.hint-label{
+  padding-left:5px;
+  color:rgba(0,0,0,.54);
+  font-size:10px;
+  padding-top:10px;
+}
+
+.my-chip .mat-chip-selected {
+  background-color: rgb(57, 181, 74) !important;
+  border: 1px solid rgb(57, 181, 74);
+  color: #fff !important;
+}
+
+.mat-chip.mat-standard-chip {
+  background: white;
+  border: 1px solid rgb(57, 181, 74);
+  color: rgb(57, 181, 74);
+}
+
+.mat-chip.mat-standard-chip::after {
+  background: white;
+}
\ No newline at end of file
diff --git 
a/ui/src/app/editor/dialog/save-pipeline/node-tag-selector/node-tag-selector.component.ts
 
b/ui/src/app/editor/dialog/save-pipeline/node-tag-selector/node-tag-selector.component.ts
new file mode 100644
index 0000000..5f91b24
--- /dev/null
+++ 
b/ui/src/app/editor/dialog/save-pipeline/node-tag-selector/node-tag-selector.component.ts
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {NodeInfoDescription} from 
"../../../../core-model/gen/streampipes-model";
+
+export interface NodeTags {
+  name: string;
+  selected: boolean;
+}
+@Component({
+  selector: 'node-tag-selector',
+  templateUrl: './node-tag-selector.component.html',
+  styleUrls: ['./node-tag-selector.component.scss']
+})
+export class NodeTagSelectorComponent implements OnInit {
+
+  @Input()
+  nodes: NodeInfoDescription[];
+
+  @Output()
+  createDynamicallySelectedTags: EventEmitter<NodeInfoDescription[]> = new 
EventEmitter<NodeInfoDescription[]>();
+
+  filteredNodes: NodeInfoDescription[] = [];
+  nodeTags: NodeTags[] = [];
+  dynamicallySelectedTags: NodeTags["name"][] = [];
+
+  ngOnInit(): void {
+    this.nodes.forEach(node => {
+      if (node.supportedElements.length > 0 || 
node.registeredContainers.length > 0) {
+        for (let tag of node.staticNodeMetadata.locationTags) {
+          if (!this.nodeTags.some(n => n.name === tag)) {
+            this.nodeTags.push({'name': tag, selected: false})
+          }
+        }
+      }
+    })
+  }
+
+  onSelectTag(tag: NodeTags) {
+    tag.selected=!tag.selected
+    this.dynamicallySelectedTags = [];
+    for (let tag of this.nodeTags) {
+      if(tag.selected){
+        this.dynamicallySelectedTags.push(tag.name);
+      }
+    }
+    this.filterAndEmitNodes();
+  }
+
+  filterAndEmitNodes() {
+    this.filteredNodes = [];
+     this.nodes.forEach(node => {
+      node.staticNodeMetadata.locationTags.forEach(tag => {
+        if (this.dynamicallySelectedTags.includes(tag)) {
+          if (!this.filteredNodes.includes(node)) {
+            this.filteredNodes.push(node);
+          }
+        }
+      });
+    });
+    this.createDynamicallySelectedTags.emit(this.filteredNodes);
+  }
+}
diff --git 
a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html 
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
index 4f16b6f..1ccb9a7 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
+++ b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
@@ -55,7 +55,7 @@
                         <b>Pipeline Operation Policies</b>
                     </div>
 
-                    <div style="margin-top: 2em">
+                    <div style="margin-top: 1em">
                         <div fxFlex="100" fxLayout="row">
                             <div fxFlex="50" fxLayout="row" 
fxLayoutAlign="left top">
                                 <span>Relay Policy</span>
@@ -93,9 +93,13 @@
 
                     <div style="margin: 1em 0 1em 0;">
                         <div>
-                            <b>Node Execution Targets</b>
+                            <b>Deployment Targets</b>
+                        </div>
+                        <div style="margin: 1em 0 1em 0;"  
*ngIf="selectedPipelineExecutionPolicy=='custom'">
+                            <node-tag-selector [nodes]="edgeNodes"
+                                               
(createDynamicallySelectedTags)="nodesFromSelectedTags($event)"></node-tag-selector>
                         </div>
-                        <div style="margin-top: 2em">
+                        <div style="margin-top: 1em">
                             <mat-accordion class="example-headers-align">
 
                                 <mat-expansion-panel (opened)="panelOpenState 
= true"
@@ -119,9 +123,10 @@
                                                 <mat-select 
[(ngModel)]="processors.deploymentTargetNodeId"
                                                             
[disabled]="disableNodeSelection.value"
                                                             required>
-                                                    <mat-option 
[value]="nodeInfo.nodeControllerId"
-                                                                *ngFor="let 
nodeInfo of deploymentOptions[processors.appId]">
-                                                        
<em>{{nodeInfo.hostname}}</em>
+                                                    <mat-option
+                                                            *ngFor="let node 
of deploymentOptions[processors.appId]"
+                                                            
[value]="node.nodeControllerId">
+                                                        
<em>{{node.hostname}}</em>
                                                     </mat-option>
                                                 </mat-select>
                                             </mat-form-field>
@@ -138,9 +143,10 @@
                                                 <mat-select 
[(ngModel)]="sinks.deploymentTargetNodeId"
                                                             
[disabled]="disableNodeSelection.value"
                                                             required>
-                                                    <mat-option 
[value]="nodeInfo.nodeControllerId"
-                                                                *ngFor="let 
nodeInfo of deploymentOptions[sinks.appId]">
-                                                        
<em>{{nodeInfo.hostname}}</em>
+                                                    <mat-option
+                                                            *ngFor="let node 
of deploymentOptions[sinks.appId]"
+                                                            
[value]="node.nodeControllerId">
+                                                        
<em>{{node.hostname}}</em>
                                                     </mat-option>
                                                 </mat-select>
                                             </mat-form-field>
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 2571ead..2b7cf7a 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,13 @@ import {
   Message,
   Pipeline,
   NodeInfoDescription,
-  StaticNodeMetadata, NvidiaContainerRuntime, DockerContainerRuntime, 
ContainerRuntime, DataSinkInvocation
+  StaticNodeMetadata,
+  NvidiaContainerRuntime,
+  DockerContainerRuntime,
+  ContainerRuntime,
+  DataSinkInvocation,
+  SpDataStream,
+  SpDataStreamUnion
 } from "../../../core-model/gen/streampipes-model";
 import {ObjectProvider} from "../../services/object-provider.service";
 import {EditorService} from "../../services/editor.service";
@@ -57,6 +63,8 @@ export class SavePipelineComponent implements OnInit {
   tmpPipeline: Pipeline;
   panelOpenState: boolean;
 
+  filteredNodes = new FormControl();
+
   pipelineExecutionPolicies: string[] = ['default', 'locality-aware', 
'custom'];
 
   @Input()
@@ -111,7 +119,7 @@ export class SavePipelineComponent implements OnInit {
     } else {
       this.selectedRelayStrategyVal = "buffer";
       this.selectedPipelineExecutionPolicy = "locality-aware";
-      this.applyLocalityAwarePolicy(this.tmpPipeline.streams, 
this.tmpPipeline.sepas);
+      this.applyLocalityAwarePolicy();
     }
   }
 
@@ -146,23 +154,55 @@ export class SavePipelineComponent implements OnInit {
     });
   };
 
-  applyLocalityAwarePolicy(streams, pipelineElements) {
-    streams.forEach(s => {
+  applyLocalityAwarePolicy() {
+    this.tmpPipeline.streams.forEach(s => {
       //let processors: DataProcessorInvocation[];
       //processors = this.pipeline.sepas.filter(p => p.connectedTo.some(entry 
=> entry == s.dom));
-      pipelineElements.forEach(p => {
-        p.deploymentTargetNodeId = s.deploymentTargetNodeId;
-        p.deploymentTargetNodeHostname = s.deploymentTargetNodeHostname;
-        p.deploymentTargetNodePort = s.deploymentTargetNodePort;
+      this.tmpPipeline.sepas.forEach(processor => {
+        processor.deploymentTargetNodeId = s.deploymentTargetNodeId;
+        processor.deploymentTargetNodeHostname = 
s.deploymentTargetNodeHostname;
+        processor.deploymentTargetNodePort = s.deploymentTargetNodePort;
       });
+
+    //   this.tmpPipeline.actions.forEach(p => {
+    //     p.deploymentTargetNodeId = s.deploymentTargetNodeId;
+    //     p.deploymentTargetNodeHostname = s.deploymentTargetNodeHostname;
+    //     p.deploymentTargetNodePort = s.deploymentTargetNodePort;
+    //   });
     });
   }
 
-  private applyDefaultPolicy(pipelineElements) {
-    pipelineElements.forEach(p => {
+  private applyTagBasedPolicy(filteredNodes: NodeInfoDescription[]) {
+    if (filteredNodes.length > 0) {
+
+      this.tmpPipeline.sepas.forEach(processor => {
+        this.deploymentOptions[processor.appId] = [];
+
+        filteredNodes.forEach(filteredNode => {
+
+          if (filteredNode.supportedElements.length != 0 &&
+              filteredNode.supportedElements.some(appId => appId === 
processor.appId)) {
+            this.deploymentOptions[processor.appId].push(filteredNode);
+          }
+        })
+      })
+
+    } else {
+      this.addAppIds(this.tmpPipeline.sepas, this.edgeNodes);
+      this.addAppIds(this.tmpPipeline.actions, this.edgeNodes);
+    }
+  }
+
+  private applyDefaultPolicy() {
+    this.tmpPipeline.sepas.forEach(p => {
       p.deploymentTargetNodeId = "default";
       // this.deploymentOptions[p.appId].push(this.makeDefaultNodeInfo());
     });
+
+    // this.tmpPipeline.actions.forEach(p => {
+    //   p.deploymentTargetNodeId = "default";
+    //   // this.deploymentOptions[p.appId].push(this.makeDefaultNodeInfo());
+    // });
   }
 
   loadAndPrepareEdgeNodes() {
@@ -303,11 +343,24 @@ export class SavePipelineComponent implements OnInit {
     } else if (value == "locality-aware") {
       this.panelOpenState = false;
       this.disableNodeSelection.setValue(true);
-      this.applyLocalityAwarePolicy(this.tmpPipeline.streams, 
this.tmpPipeline.sepas)
+      this.applyLocalityAwarePolicy()
     } else if (value == "default") {
       this.panelOpenState = false;
       this.disableNodeSelection.setValue(true);
-      this.applyDefaultPolicy(this.tmpPipeline.sepas);
+      this.applyDefaultPolicy();
     }
+    // else if (value == "tag-based") {
+    //   this.panelOpenState = true;
+    //   this.disableNodeSelection.setValue(false);
+    // }
+  }
+
+  nodesFromSelectedTags(filteredNodes: NodeInfoDescription[]) {
+    this.applyTagBasedPolicy(filteredNodes)
+  }
+
+
+  compareFn(c1: any, c2:any): boolean {
+    return c1 && c2 ? c1.id === c2.id : c1 === c2;
   }
 }
\ No newline at end of file
diff --git a/ui/src/app/editor/editor.module.ts 
b/ui/src/app/editor/editor.module.ts
index 71e291d..ace0a0f 100644
--- a/ui/src/app/editor/editor.module.ts
+++ b/ui/src/app/editor/editor.module.ts
@@ -67,6 +67,8 @@ import {PipelineElementDraggedService} from 
"./services/pipeline-element-dragged
 import {PipelineCanvasScrollingService} from 
"./services/pipeline-canvas-scrolling.service";
 import {JsplumbEndpointService} from "./services/jsplumb-endpoint.service";
 import {JsplumbFactoryService} from "./services/jsplumb-factory.service";
+import { NodeTagSelectorComponent } from 
'./dialog/save-pipeline/node-tag-selector/node-tag-selector.component';
+import {MatChipsModule} from "@angular/material/chips";
 
 @NgModule({
     imports: [
@@ -86,6 +88,7 @@ import {JsplumbFactoryService} from 
"./services/jsplumb-factory.service";
         ReactiveFormsModule,
         MatSliderModule,
         ReactiveFormsModule,
+        MatChipsModule,
     ],
     declarations: [
         CompatibleElementsComponent,
@@ -110,7 +113,8 @@ import {JsplumbFactoryService} from 
"./services/jsplumb-factory.service";
         SavePipelineComponent,
         SafeCss,
         WelcomeTourComponent,
-        MigratePipelineProcessorsComponent
+        MigratePipelineProcessorsComponent,
+        NodeTagSelectorComponent
     ],
     providers: [
         EditorService,

Reply via email to