You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2022/07/16 18:58:34 UTC

[incubator-streampipes] branch STREAMPIPES-558 updated: [STREAMPIPES-559] Support templates for adapters

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

riemer pushed a commit to branch STREAMPIPES-558
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git


The following commit(s) were added to refs/heads/STREAMPIPES-558 by this push:
     new cf8101915 [STREAMPIPES-559] Support templates for adapters
cf8101915 is described below

commit cf81019158e14e7ba22849541d812482c364fe17
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Sat Jul 16 20:58:21 2022 +0200

    [STREAMPIPES-559] Support templates for adapters
---
 ...teHandler.java => AbstractTemplateHandler.java} | 37 +++++----
 .../manager/template/AdapterTemplateHandler.java   | 46 +++++++++++
 .../template/PipelineElementTemplateHandler.java   | 33 +++-----
 .../template/PipelineElementTemplateVisitor.java   |  6 +-
 .../ps/PipelineElementTemplateResource.java        | 30 +++++++
 ui/cypress.json                                    |  2 +-
 .../lib/apis/pipeline-element-template.service.ts  |  8 ++
 .../new-adapter/adapter-configuration.directive.ts | 96 ++++++++++++++++++++++
 .../generic-adapter-configuration.component.html   | 25 +++++-
 .../generic-adapter-configuration.component.ts     | 52 ++++++------
 .../event-property-row.component.ts                | 10 ---
 .../specific-adapter-configuration.component.html  | 22 ++++-
 .../specific-adapter-configuration.component.ts    | 48 +++++------
 ui/src/app/connect/connect.module.ts               |  2 +
 .../adapter-template-dialog.component.html}        | 35 ++++----
 .../adapter-template-dialog.component.scss}        | 15 ----
 .../adapter-template-dialog.component.ts           | 71 ++++++++++++++++
 .../connect/services/adapter-template.service.ts   | 45 ++++++++++
 ui/src/app/core-ui/core-ui.module.ts               |  5 ++
 ...pipeline-element-template-config.component.html | 11 +--
 ...pipeline-element-template-config.component.scss |  2 +-
 .../pipeline-element-template-config.component.ts  | 14 ++--
 .../pipeline-element-template-generator.ts         |  8 +-
 .../pipeline-element-template.pipe.ts}             | 27 +++---
 .../static-free-input.component.ts                 | 15 ++--
 ...tic-runtime-resolvable-any-input.component.html |  2 +-
 ...c-runtime-resolvable-oneof-input.component.html |  2 +-
 .../static-secret-input.component.html             |  4 +-
 .../dialog/customize/customize.component.html      |  9 +-
 .../editor/dialog/customize/customize.component.ts |  3 -
 ui/src/app/editor/editor.module.ts                 |  6 +-
 31 files changed, 504 insertions(+), 187 deletions(-)

diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateHandler.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/AbstractTemplateHandler.java
similarity index 53%
copy from streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateHandler.java
copy to streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/AbstractTemplateHandler.java
index 97881fac8..b53ba7042 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateHandler.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/AbstractTemplateHandler.java
@@ -15,40 +15,43 @@
  * limitations under the License.
  *
  */
+
 package org.apache.streampipes.manager.template;
 
-import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
 import org.apache.streampipes.model.template.PipelineElementTemplate;
 
 import java.util.HashMap;
 import java.util.Map;
 
-public abstract class PipelineElementTemplateHandler<T extends InvocableStreamPipesEntity> {
+public abstract class AbstractTemplateHandler<T> {
 
-  protected T pipelineElement;
+  protected T element;
   protected PipelineElementTemplate template;
 
-  private boolean overwriteNameAndDescription;
+  protected boolean overwriteNameAndDescription;
 
-  public PipelineElementTemplateHandler(PipelineElementTemplate template,
-                                        T pipelineElement,
-                                        boolean overwriteNameAndDescription) {
+  public AbstractTemplateHandler(PipelineElementTemplate template,
+                                 T element,
+                                 boolean overwriteNameAndDescription) {
     this.template = template;
-    this.pipelineElement = pipelineElement;
+    this.element = element;
     this.overwriteNameAndDescription = overwriteNameAndDescription;
   }
 
   public T applyTemplateOnPipelineElement() {
-      Map<String, Object> configs = new HashMap<>();
-      template.getTemplateConfigs().forEach((key, value) -> configs.put(key, value.getValue()));
-      PipelineElementTemplateVisitor visitor = new PipelineElementTemplateVisitor(configs);
-      pipelineElement.getStaticProperties().forEach(config -> config.accept(visitor));
+    Map<String, Object> configs = new HashMap<>();
+    template.getTemplateConfigs().forEach((key, value) -> configs.put(key, value.getValue()));
+    PipelineElementTemplateVisitor visitor = new PipelineElementTemplateVisitor(configs);
+    visitStaticProperties(visitor);
 
-      if (overwriteNameAndDescription) {
-        pipelineElement.setName(template.getTemplateName());
-        pipelineElement.setDescription(template.getTemplateDescription());
-      }
+    if (overwriteNameAndDescription) {
+      applyNameAndDescription(template.getTemplateName(), template.getTemplateDescription());
+    }
 
-      return pipelineElement;
+    return element;
   }
+
+  protected abstract void visitStaticProperties(PipelineElementTemplateVisitor visitor);
+
+  protected abstract void applyNameAndDescription(String name, String description);
 }
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/AdapterTemplateHandler.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/AdapterTemplateHandler.java
new file mode 100644
index 000000000..c3aa7d3a9
--- /dev/null
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/AdapterTemplateHandler.java
@@ -0,0 +1,46 @@
+/*
+ * 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.template;
+
+import org.apache.streampipes.model.connect.adapter.*;
+import org.apache.streampipes.model.template.PipelineElementTemplate;
+
+public class AdapterTemplateHandler extends AbstractTemplateHandler<AdapterDescription> {
+
+  public AdapterTemplateHandler(PipelineElementTemplate template,
+                                        AdapterDescription adapterDescription,
+                                        boolean overwriteNameAndDescription) {
+    super(template, adapterDescription, overwriteNameAndDescription);
+  }
+
+  @Override
+  protected void visitStaticProperties(PipelineElementTemplateVisitor visitor) {
+    if (element instanceof SpecificAdapterStreamDescription || element instanceof SpecificAdapterSetDescription) {
+      element.getConfig().forEach(config -> config.accept(visitor));
+    } else if (element instanceof GenericAdapterSetDescription || element instanceof GenericAdapterStreamDescription) {
+      ((GenericAdapterDescription) element).getProtocolDescription().getConfig().forEach(config -> config.accept(visitor));
+    }
+  }
+
+  @Override
+  protected void applyNameAndDescription(String name, String description) {
+    element.setName(name);
+    element.setDescription(description);
+  }
+}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateHandler.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateHandler.java
index 97881fac8..1269f3506 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateHandler.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateHandler.java
@@ -20,35 +20,22 @@ package org.apache.streampipes.manager.template;
 import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
 import org.apache.streampipes.model.template.PipelineElementTemplate;
 
-import java.util.HashMap;
-import java.util.Map;
-
-public abstract class PipelineElementTemplateHandler<T extends InvocableStreamPipesEntity> {
-
-  protected T pipelineElement;
-  protected PipelineElementTemplate template;
-
-  private boolean overwriteNameAndDescription;
+public abstract class PipelineElementTemplateHandler<T extends InvocableStreamPipesEntity> extends AbstractTemplateHandler<T> {
 
   public PipelineElementTemplateHandler(PipelineElementTemplate template,
                                         T pipelineElement,
                                         boolean overwriteNameAndDescription) {
-    this.template = template;
-    this.pipelineElement = pipelineElement;
-    this.overwriteNameAndDescription = overwriteNameAndDescription;
+    super(template, pipelineElement, overwriteNameAndDescription);
   }
 
-  public T applyTemplateOnPipelineElement() {
-      Map<String, Object> configs = new HashMap<>();
-      template.getTemplateConfigs().forEach((key, value) -> configs.put(key, value.getValue()));
-      PipelineElementTemplateVisitor visitor = new PipelineElementTemplateVisitor(configs);
-      pipelineElement.getStaticProperties().forEach(config -> config.accept(visitor));
-
-      if (overwriteNameAndDescription) {
-        pipelineElement.setName(template.getTemplateName());
-        pipelineElement.setDescription(template.getTemplateDescription());
-      }
+  protected void visitStaticProperties(PipelineElementTemplateVisitor visitor) {
+    element.getStaticProperties().forEach(config -> config.accept(visitor));
+  }
 
-      return pipelineElement;
+  protected void applyNameAndDescription(String name, String description) {
+    element.setName(name);
+    element.setDescription(description);
   }
+
+
 }
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java
index 5da9a2b5f..4c533aa45 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java
@@ -127,8 +127,10 @@ public class PipelineElementTemplateVisitor implements StaticPropertyVisitor {
       Map<String, Object> values = getAsMap(staticPropertyAlternative);
       StaticProperty property = staticPropertyAlternative.getStaticProperty();
       staticPropertyAlternative.setSelected(Boolean.parseBoolean(String.valueOf(values.get("selected"))));
-      PipelineElementTemplateVisitor visitor = new PipelineElementTemplateVisitor(getAsMap(values, "staticProperty"));
-      property.accept(visitor);
+      if (property != null) {
+        PipelineElementTemplateVisitor visitor = new PipelineElementTemplateVisitor(getAsMap(values, "staticProperty"));
+        property.accept(visitor);
+      }
     }
   }
 
diff --git a/streampipes-platform-services/src/main/java/org/apache/streampipes/ps/PipelineElementTemplateResource.java b/streampipes-platform-services/src/main/java/org/apache/streampipes/ps/PipelineElementTemplateResource.java
index dfa5bbcee..d159e2e46 100644
--- a/streampipes-platform-services/src/main/java/org/apache/streampipes/ps/PipelineElementTemplateResource.java
+++ b/streampipes-platform-services/src/main/java/org/apache/streampipes/ps/PipelineElementTemplateResource.java
@@ -24,8 +24,10 @@ import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.parameters.RequestBody;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import org.apache.streampipes.manager.template.AdapterTemplateHandler;
 import org.apache.streampipes.manager.template.DataProcessorTemplateHandler;
 import org.apache.streampipes.manager.template.DataSinkTemplateHandler;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
 import org.apache.streampipes.model.graph.DataProcessorInvocation;
 import org.apache.streampipes.model.graph.DataSinkInvocation;
 import org.apache.streampipes.model.template.PipelineElementTemplate;
@@ -194,4 +196,32 @@ public class PipelineElementTemplateResource extends AbstractRestResource {
     return ok(new DataProcessorTemplateHandler(template, invocation, Boolean.parseBoolean(overwriteNameAndDescription))
             .applyTemplateOnPipelineElement());
   }
+
+  @POST
+  @Path("{id}/adapter")
+  @Produces(MediaType.APPLICATION_JSON)
+  @Consumes(MediaType.APPLICATION_JSON)
+  @JacksonSerialized
+  @Operation(summary = "Configure an adapter with a pipeline element template.",
+    tags = {"Pipeline Element Templates"},
+    responses = {
+      @ApiResponse(content = {
+        @Content(
+          mediaType = "application/json",
+          schema = @Schema(implementation = AdapterDescription.class))
+      }, responseCode = "200", description = "The configured adapter model"),
+    })
+  public Response getPipelineElementForTemplate(@Parameter(description = "The id of the pipeline element template", required = true)
+                                                @PathParam("id") String id,
+
+                                                @Parameter(description = "Overwrite the name and description of the pipeline element with the labels given in the pipeline element template")
+                                                @QueryParam("overwriteNames") String overwriteNameAndDescription,
+
+                                                @RequestBody(description = "The adapter that should be configured with the template contents",
+                                                  content = @Content(schema = @Schema(implementation = AdapterDescription.class))) AdapterDescription adapterDescription) {
+    PipelineElementTemplate template = getPipelineElementTemplateStorage().getElementById(id);
+    var desc = new AdapterTemplateHandler(template, adapterDescription, Boolean.parseBoolean(overwriteNameAndDescription))
+      .applyTemplateOnPipelineElement();
+    return ok(desc);
+  }
 }
diff --git a/ui/cypress.json b/ui/cypress.json
index b36abd549..02563ffcb 100644
--- a/ui/cypress.json
+++ b/ui/cypress.json
@@ -8,7 +8,7 @@
     "runMode": 1,
     "openMode": 0
   },
-  "baseUrl": "http://localhost:80",
+  "baseUrl": "http://localhost:8082",
   "trashAssetsBeforeRuns": false,
   "videoCompression": false,
   "viewportWidth": 1920,
diff --git a/ui/projects/streampipes/platform-services/src/lib/apis/pipeline-element-template.service.ts b/ui/projects/streampipes/platform-services/src/lib/apis/pipeline-element-template.service.ts
index 4bfef5c5f..d54250409 100644
--- a/ui/projects/streampipes/platform-services/src/lib/apis/pipeline-element-template.service.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/apis/pipeline-element-template.service.ts
@@ -20,6 +20,8 @@ import { Injectable } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 import {
+  AdapterDescription,
+  AdapterDescriptionUnion,
   DataProcessorInvocation,
   DataSinkInvocation,
   PipelineElementTemplate
@@ -62,6 +64,12 @@ export class PipelineElementTemplateService {
         }));
   }
 
+  getConfiguredAdapterForTemplate(templateId: string,
+                                  adapter: AdapterDescriptionUnion): Observable<any> {
+    return this.http.post(this.platformServicesCommons.apiBasePath
+      + '/pipeline-element-templates/' + templateId + '/adapter', adapter);
+  }
+
   storePipelineElementTemplate(template: PipelineElementTemplate) {
     return this.http.post(this.platformServicesCommons.apiBasePath + '/pipeline-element-templates', template);
   }
diff --git a/ui/src/app/connect/components/new-adapter/adapter-configuration.directive.ts b/ui/src/app/connect/components/new-adapter/adapter-configuration.directive.ts
new file mode 100644
index 000000000..c6f7ac28e
--- /dev/null
+++ b/ui/src/app/connect/components/new-adapter/adapter-configuration.directive.ts
@@ -0,0 +1,96 @@
+/*
+ * 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 { Directive, EventEmitter, Input, Output } from '@angular/core';
+import { MatStepper } from '@angular/material/stepper';
+import {
+  AdapterDescriptionUnion,
+  PipelineElementTemplate,
+  PipelineElementTemplateService
+} from '@streampipes/platform-services';
+import { FormBuilder } from '@angular/forms';
+import { AdapterTemplateService } from '../../services/adapter-template.service';
+import { DialogService } from '@streampipes/shared-ui';
+
+@Directive()
+export abstract class AdapterConfigurationDirective {
+
+  /**
+   * Adapter description the selected format is added to
+   */
+  @Input() adapterDescription: AdapterDescriptionUnion;
+
+  cachedAdapterDescription: AdapterDescriptionUnion;
+
+  /**
+   * Cancels the adapter configuration process
+   */
+  @Output() removeSelectionEmitter: EventEmitter<boolean> = new EventEmitter();
+
+  /**
+   * Go to next configuration step when this is complete
+   */
+  @Output() clickNextEmitter: EventEmitter<MatStepper> = new EventEmitter();
+
+  availableTemplates: PipelineElementTemplate[];
+  selectedTemplate: any = false;
+
+  constructor(protected _formBuilder: FormBuilder,
+              protected dialogService: DialogService,
+              protected pipelineElementTemplateService: PipelineElementTemplateService,
+              protected adapterTemplateService: AdapterTemplateService) {
+  }
+
+  onInit(): void {
+    this.loadPipelineElementTemplates();
+  }
+
+  public removeSelection() {
+    this.removeSelectionEmitter.emit();
+  }
+
+  public clickNext() {
+    this.clickNextEmitter.emit();
+  }
+
+  loadPipelineElementTemplates() {
+    this.pipelineElementTemplateService
+      .getPipelineElementTemplates(this.adapterDescription.appId)
+      .subscribe(templates => {
+        this.availableTemplates = templates;
+      });
+  }
+
+  loadTemplate(event: any) {
+    if (!event.value) {
+      this.adapterDescription = {...this.cachedAdapterDescription};
+      this.selectedTemplate = false;
+    } else {
+      this.selectedTemplate = event.value;
+      this.pipelineElementTemplateService.getConfiguredAdapterForTemplate(event.value._id, this.adapterDescription)
+        .subscribe(adapterDescription => {
+          this.afterTemplateReceived(adapterDescription);
+        });
+    }
+  }
+
+  abstract openTemplateDialog(): void;
+
+  abstract afterTemplateReceived(adapterDescription: any);
+
+}
diff --git a/ui/src/app/connect/components/new-adapter/generic-adapter-configuration/generic-adapter-configuration.component.html b/ui/src/app/connect/components/new-adapter/generic-adapter-configuration/generic-adapter-configuration.component.html
index 75276dd70..9dd1a3d13 100644
--- a/ui/src/app/connect/components/new-adapter/generic-adapter-configuration/generic-adapter-configuration.component.html
+++ b/ui/src/app/connect/components/new-adapter/generic-adapter-configuration/generic-adapter-configuration.component.html
@@ -20,6 +20,21 @@
     <div fxFlex="100" fxLayout="column">
         <div fxFlex="100" fxLayout="column">
             <sp-basic-inner-panel panelTitle="Protocol Settings" outerMargin="20px 0px">
+                <div header fxFlex="100" fxLayout="row" fxLayoutAlign="end center" class="pr-5">
+                    <div fxFlex="100" fxLayout="row" fxLayoutAlign="end center"
+                         *ngIf="availableTemplates && availableTemplates.length > 0">
+                        <mat-form-field class="form-field" floatLabel="never" color="accent">
+                            <mat-label>Use template</mat-label>
+                            <mat-select (selectionChange)="loadTemplate($event)"
+                                        [(value)]="selectedTemplate">
+                                <mat-option>--</mat-option>
+                                <mat-option [value]="template" *ngFor="let template of availableTemplates">
+                                    {{template.templateName}}
+                                </mat-option>
+                            </mat-select>
+                        </mat-form-field>
+                    </div>
+                </div>
                 <sp-configuration-group
                         [configurationGroup]="genericAdapterForm"
                         [adapterId]="adapterDescription.appId"
@@ -30,7 +45,15 @@
     </div>
 
     <div fxLayoutAlign="end">
-        <button class="mat-basic" mat-raised-button (click)="removeSelection()">Cancel</button>
+        <button class="mat-basic"
+                mat-raised-button
+                (click)="removeSelection()"
+                style="margin-right: 10px;">Cancel
+        </button>
+        <button mat-button mat-raised-button
+                class="mat-basic"
+                (click)="openTemplateDialog()">Store config as template
+        </button>
         <div id="generic-settings-next-button">
             <button class="stepper-button" [disabled]="!genericAdapterSettingsFormValid"
                     (click)="clickNext()" color="accent" mat-raised-button>Next
diff --git a/ui/src/app/connect/components/new-adapter/generic-adapter-configuration/generic-adapter-configuration.component.ts b/ui/src/app/connect/components/new-adapter/generic-adapter-configuration/generic-adapter-configuration.component.ts
index 574620109..ff6df2c46 100644
--- a/ui/src/app/connect/components/new-adapter/generic-adapter-configuration/generic-adapter-configuration.component.ts
+++ b/ui/src/app/connect/components/new-adapter/generic-adapter-configuration/generic-adapter-configuration.component.ts
@@ -16,38 +16,24 @@
  *
  */
 
-import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import {
-  AdapterDescriptionUnion,
   GenericAdapterSetDescription,
   GenericAdapterStreamDescription,
-  ProtocolDescription
+  ProtocolDescription,
+  PipelineElementTemplateService
 } from '@streampipes/platform-services';
 import { FormBuilder, FormGroup } from '@angular/forms';
-import { MatStepper } from '@angular/material/stepper';
+import { AdapterConfigurationDirective } from '../adapter-configuration.directive';
+import { AdapterTemplateService } from '../../../services/adapter-template.service';
+import { DialogService } from '@streampipes/shared-ui';
 
 @Component({
   selector: 'sp-generic-adapter-configuration',
   templateUrl: './generic-adapter-configuration.component.html',
   styleUrls: ['./generic-adapter-configuration.component.scss']
 })
-export class GenericAdapterConfigurationComponent implements OnInit {
-
-  /**
-   * Adapter description the selected format is added to
-   */
-  @Input() adapterDescription: AdapterDescriptionUnion;
-
-  /**
-   * Cancels the adapter configuration process
-   */
-  @Output() removeSelectionEmitter: EventEmitter<boolean> = new EventEmitter();
-
-  /**
-   * Go to next configuration step when this is complete
-   */
-  @Output() clickNextEmitter: EventEmitter<MatStepper> = new EventEmitter();
-
+export class GenericAdapterConfigurationComponent extends AdapterConfigurationDirective implements OnInit {
 
   genericAdapterSettingsFormValid: boolean;
 
@@ -55,12 +41,15 @@ export class GenericAdapterConfigurationComponent implements OnInit {
 
   protocolDescription: ProtocolDescription;
 
-  constructor(
-    private _formBuilder: FormBuilder
-  ) { }
+  constructor(_formBuilder: FormBuilder,
+              dialogService: DialogService,
+              pipelineElementTemplateService: PipelineElementTemplateService,
+              adapterTemplateService: AdapterTemplateService) {
+    super(_formBuilder, dialogService, pipelineElementTemplateService, adapterTemplateService);
+  }
 
   ngOnInit(): void {
-
+    super.onInit();
     if (this.adapterDescription instanceof GenericAdapterSetDescription ||
       this.adapterDescription instanceof GenericAdapterStreamDescription) {
       this.protocolDescription = this.adapterDescription.protocolDescription;
@@ -73,11 +62,16 @@ export class GenericAdapterConfigurationComponent implements OnInit {
     });
   }
 
-  public removeSelection() {
-    this.removeSelectionEmitter.emit();
+  openTemplateDialog(): void {
+    const dialogRef = this.adapterTemplateService.getDialog(this.protocolDescription.config, this.protocolDescription.appId);
+
+    dialogRef.afterClosed().subscribe(refresh => {
+      this.loadPipelineElementTemplates();
+    });
   }
 
-  public clickNext() {
-    this.clickNextEmitter.emit();
+  afterTemplateReceived(adapterDescription: any) {
+    this.protocolDescription = ProtocolDescription.fromData(adapterDescription.protocolDescription);
   }
+
 }
diff --git a/ui/src/app/connect/components/new-adapter/schema-editor/event-property-row/event-property-row.component.ts b/ui/src/app/connect/components/new-adapter/schema-editor/event-property-row/event-property-row.component.ts
index 6a0c178bd..c4fb32923 100644
--- a/ui/src/app/connect/components/new-adapter/schema-editor/event-property-row/event-property-row.component.ts
+++ b/ui/src/app/connect/components/new-adapter/schema-editor/event-property-row/event-property-row.component.ts
@@ -125,16 +125,6 @@ export class EventPropertyRowComponent implements OnInit, OnChanges {
         this.timestampProperty = this.isTimestampProperty(this.node.data);
         this.refreshTreeEmitter.emit();
     });
-    // const dialogRef = this.dialog.open(EditEventPropertyComponent, {
-    //   data: {
-    //     property: data,
-    //     isEditable: this.isEditable
-    //   }
-    // });
-    // dialogRef.afterClosed().subscribe(result => {
-    //   this.timestampProperty = this.isTimestampProperty(this.node.data);
-    //   this.refreshTreeEmitter.emit();
-    // });
   }
 
   public selectProperty(id: string, eventProperties: any): void {
diff --git a/ui/src/app/connect/components/new-adapter/specific-adapter-configuration/specific-adapter-configuration.component.html b/ui/src/app/connect/components/new-adapter/specific-adapter-configuration/specific-adapter-configuration.component.html
index a6fc0b32d..5754e1c8d 100644
--- a/ui/src/app/connect/components/new-adapter/specific-adapter-configuration/specific-adapter-configuration.component.html
+++ b/ui/src/app/connect/components/new-adapter/specific-adapter-configuration/specific-adapter-configuration.component.html
@@ -19,6 +19,20 @@
 <div fxFlex="100" fxLayout="column">
     <div fxFlex="100" fxLayout="column">
         <sp-basic-inner-panel panelTitle="Basic Settings" outerMargin="20px 0px">
+            <div header fxFlex="100" fxLayout="row" fxLayoutAlign="end center" class="pr-5">
+                <div fxFlex="100" fxLayout="row" fxLayoutAlign="end center" *ngIf="availableTemplates && availableTemplates.length > 0">
+                    <mat-form-field class="form-field" floatLabel="never" color="accent">
+                        <mat-label>Use template</mat-label>
+                        <mat-select (selectionChange)="loadTemplate($event)"
+                                    [(value)]="selectedTemplate">
+                            <mat-option>--</mat-option>
+                            <mat-option [value]="template" *ngFor="let template of availableTemplates">
+                                {{template.templateName}}
+                            </mat-option>
+                        </mat-select>
+                    </mat-form-field>
+                </div>
+            </div>
             <sp-configuration-group
                     [configurationGroup]="specificAdapterForm"
                     [adapterId]="adapterDescription.appId"
@@ -28,7 +42,13 @@
     </div>
 
     <div fxLayoutAlign="end">
-        <button class="mat-basic" mat-raised-button (click)="removeSelection()">Cancel</button>
+        <button class="mat-basic"
+                mat-raised-button
+                style="margin-right: 10px;"
+                (click)="removeSelection()">Cancel</button>
+        <button mat-button mat-raised-button
+                class="mat-basic"
+                (click)="openTemplateDialog()">Store config as template</button>
         <div id="specific-settings-next-button">
             <button class="stepper-button" [disabled]="!specificAdapterSettingsFormValid"
                     (click)="clickNext()" color="accent" mat-raised-button>Next
diff --git a/ui/src/app/connect/components/new-adapter/specific-adapter-configuration/specific-adapter-configuration.component.ts b/ui/src/app/connect/components/new-adapter/specific-adapter-configuration/specific-adapter-configuration.component.ts
index e848ed1c2..4e90fbeb7 100644
--- a/ui/src/app/connect/components/new-adapter/specific-adapter-configuration/specific-adapter-configuration.component.ts
+++ b/ui/src/app/connect/components/new-adapter/specific-adapter-configuration/specific-adapter-configuration.component.ts
@@ -16,41 +16,35 @@
  *
  */
 
-import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
-import { AdapterDescriptionUnion } from '@streampipes/platform-services';
+import { Component, OnInit } from '@angular/core';
+import { AdapterDescription, PipelineElementTemplateService } from '@streampipes/platform-services';
 import { FormBuilder, FormGroup } from '@angular/forms';
-import { MatStepper } from '@angular/material/stepper';
+import { DialogService } from '@streampipes/shared-ui';
+import { AdapterTemplateService } from '../../../services/adapter-template.service';
+import { AdapterConfigurationDirective } from '../adapter-configuration.directive';
 
 @Component({
   selector: 'sp-specific-adapter-configuration',
   templateUrl: './specific-adapter-configuration.component.html',
   styleUrls: ['./specific-adapter-configuration.component.scss']
 })
-export class SpecificAdapterConfigurationComponent implements OnInit {
-
-  /**
-   * Adapter description the selected format is added to
-   */
-  @Input() adapterDescription: AdapterDescriptionUnion;
-
-  /**
-   * Cancels the adapter configuration process
-   */
-  @Output() removeSelectionEmitter: EventEmitter<boolean> = new EventEmitter();
-
-  /**
-   * Go to next configuration step when this is complete
-   */
-  @Output() clickNextEmitter: EventEmitter<MatStepper> = new EventEmitter();
+export class SpecificAdapterConfigurationComponent extends AdapterConfigurationDirective implements OnInit {
 
   specificAdapterSettingsFormValid: boolean;
 
   specificAdapterForm: FormGroup;
 
-  constructor(private _formBuilder: FormBuilder) {
+
+  constructor(_formBuilder: FormBuilder,
+              dialogService: DialogService,
+              pipelineElementTemplateService: PipelineElementTemplateService,
+              adapterTemplateService: AdapterTemplateService) {
+    super(_formBuilder, dialogService, pipelineElementTemplateService, adapterTemplateService);
   }
 
   ngOnInit(): void {
+    super.onInit();
+    this.cachedAdapterDescription = {...this.adapterDescription};
     // initialize form for validation
     this.specificAdapterForm = this._formBuilder.group({});
     this.specificAdapterForm.statusChanges.subscribe((status) => {
@@ -63,11 +57,17 @@ export class SpecificAdapterConfigurationComponent implements OnInit {
     }
   }
 
-  public removeSelection() {
-    this.removeSelectionEmitter.emit();
+  openTemplateDialog(): void {
+    const dialogRef = this.adapterTemplateService.getDialog(this.adapterDescription.config, this.adapterDescription.appId);
+
+    dialogRef.afterClosed().subscribe(refresh => {
+      this.loadPipelineElementTemplates();
+    });
   }
 
-  public clickNext() {
-    this.clickNextEmitter.emit();
+  afterTemplateReceived(adapterDescription: any) {
+    this.adapterDescription = AdapterDescription.fromDataUnion(adapterDescription);
   }
+
 }
+
diff --git a/ui/src/app/connect/connect.module.ts b/ui/src/app/connect/connect.module.ts
index 4f4f64155..8fb26c7d8 100644
--- a/ui/src/app/connect/connect.module.ts
+++ b/ui/src/app/connect/connect.module.ts
@@ -84,6 +84,7 @@ import { EditSchemaTransformationComponent } from './dialog/edit-event-property/
 import { EditValueTransformationComponent } from './dialog/edit-event-property/components/edit-value-transformation/edit-value-transformation.component';
 import { SpEpSettingsSectionComponent } from './dialog/edit-event-property/components/ep-settings-section/ep-settings-section.component';
 import { SpAdapterOptionsPanelComponent } from './components/new-adapter/start-adapter-configuration/adapter-options-panel/adapter-options-panel.component';
+import { SpAdapterTemplateDialogComponent } from './dialog/adapter-template/adapter-template-dialog.component';
 
 @NgModule({
   imports: [
@@ -146,6 +147,7 @@ import { SpAdapterOptionsPanelComponent } from './components/new-adapter/start-a
     FormatItemComponent,
     FormatListComponent,
     NewAdapterComponent,
+    SpAdapterTemplateDialogComponent,
     PipelineElementRuntimeInfoComponent,
     TimestampPipe,
     EditCorrectionValueComponent,
diff --git a/ui/src/app/connect/components/new-adapter/specific-adapter-configuration/specific-adapter-configuration.component.html b/ui/src/app/connect/dialog/adapter-template/adapter-template-dialog.component.html
similarity index 51%
copy from ui/src/app/connect/components/new-adapter/specific-adapter-configuration/specific-adapter-configuration.component.html
copy to ui/src/app/connect/dialog/adapter-template/adapter-template-dialog.component.html
index a6fc0b32d..3758aaefd 100644
--- a/ui/src/app/connect/components/new-adapter/specific-adapter-configuration/specific-adapter-configuration.component.html
+++ b/ui/src/app/connect/dialog/adapter-template/adapter-template-dialog.component.html
@@ -16,23 +16,24 @@
   ~
   -->
 
-<div fxFlex="100" fxLayout="column">
-    <div fxFlex="100" fxLayout="column">
-        <sp-basic-inner-panel panelTitle="Basic Settings" outerMargin="20px 0px">
-            <sp-configuration-group
-                    [configurationGroup]="specificAdapterForm"
-                    [adapterId]="adapterDescription.appId"
-                    [configuration]="adapterDescription.config">
-            </sp-configuration-group>
-        </sp-basic-inner-panel>
-    </div>
-
-    <div fxLayoutAlign="end">
-        <button class="mat-basic" mat-raised-button (click)="removeSelection()">Cancel</button>
-        <div id="specific-settings-next-button">
-            <button class="stepper-button" [disabled]="!specificAdapterSettingsFormValid"
-                    (click)="clickNext()" color="accent" mat-raised-button>Next
-            </button>
+<div class="sp-dialog-container">
+    <div class="sp-dialog-content p-15">
+        <div fxFlex="100"
+             fxLayout="column">
+            <pipeline-element-template-config [staticProperties]="configs"
+                                              [template]="template"
+                                              [templateConfigs]="templateConfigs"
+                                              [appId]="appId">
+            </pipeline-element-template-config>
         </div>
     </div>
+    <mat-divider></mat-divider>
+    <div class="sp-dialog-actions">
+        <button mat-button mat-raised-button color="accent"
+                (click)="saveTemplate()"
+                style="margin-right: 10px;">
+            Save
+        </button>
+        <button mat-button mat-raised-button class="mat-basic" (click)="dialogRef.close()">Close</button>
+    </div>
 </div>
diff --git a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.scss b/ui/src/app/connect/dialog/adapter-template/adapter-template-dialog.component.scss
similarity index 79%
copy from ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.scss
copy to ui/src/app/connect/dialog/adapter-template/adapter-template-dialog.component.scss
index 2c1a70e95..fddade7bf 100644
--- a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.scss
+++ b/ui/src/app/connect/dialog/adapter-template/adapter-template-dialog.component.scss
@@ -17,18 +17,3 @@
  */
 
 @import '../../../../scss/sp/sp-dialog.scss';
-
-.divider {
-  margin-top: 10px;
-  margin-bottom: 10px;
-}
-
-.static-property-panel {
-  padding-left: 10px;
-  margin-bottom: 10px;
-  margin-top: 10px;
-}
-
-.static-property-panel-border {
-  border-left:5px solid gray;
-}
diff --git a/ui/src/app/connect/dialog/adapter-template/adapter-template-dialog.component.ts b/ui/src/app/connect/dialog/adapter-template/adapter-template-dialog.component.ts
new file mode 100644
index 000000000..76f3ce757
--- /dev/null
+++ b/ui/src/app/connect/dialog/adapter-template/adapter-template-dialog.component.ts
@@ -0,0 +1,71 @@
+/*
+ * 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 {
+  PipelineElementTemplate,
+  PipelineElementTemplateConfig,
+  PipelineElementTemplateService,
+  StaticPropertyUnion
+} from '@streampipes/platform-services';
+import { DialogRef } from '@streampipes/shared-ui';
+
+@Component({
+  selector: 'sp-adapter-template-dialog',
+  templateUrl: './adapter-template-dialog.component.html',
+  styleUrls: ['./adapter-template-dialog.component.scss']
+})
+export class SpAdapterTemplateDialogComponent implements OnInit {
+
+  @Input()
+  configs: StaticPropertyUnion[];
+
+  @Input()
+  appId: string;
+
+  template: PipelineElementTemplate;
+  templateConfigs: Map<string, any> = new Map();
+
+  constructor(public dialogRef: DialogRef<SpAdapterTemplateDialogComponent>,
+              private pipelineElementTemplateService: PipelineElementTemplateService) {
+
+  }
+
+  ngOnInit(): void {
+    this.template = new PipelineElementTemplate();
+  }
+
+  saveTemplate() {
+    this.template.templateConfigs = this.convert(this.templateConfigs);
+    this.pipelineElementTemplateService.storePipelineElementTemplate(this.template).subscribe(result => {
+      this.dialogRef.close(true);
+    });
+  }
+
+  convert(templateConfigs: Map<string, any>): any {
+    const configs: { [index: string]: PipelineElementTemplateConfig } = {};
+    templateConfigs.forEach((value, key) => {
+      configs[key] = new PipelineElementTemplateConfig();
+      configs[key].editable = value.editable;
+      configs[key].displayed = value.displayed;
+      configs[key].value = value.value;
+    });
+    return configs;
+  }
+
+}
diff --git a/ui/src/app/connect/services/adapter-template.service.ts b/ui/src/app/connect/services/adapter-template.service.ts
new file mode 100644
index 000000000..d7c0815c6
--- /dev/null
+++ b/ui/src/app/connect/services/adapter-template.service.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 { SpAdapterTemplateDialogComponent } from '../dialog/adapter-template/adapter-template-dialog.component';
+import { DialogService, PanelType } from '@streampipes/shared-ui';
+import { AdapterDescriptionUnion } from '@streampipes/platform-services';
+import { StaticPropertyUnion } from '../../../../projects/streampipes/platform-services/src/lib/model/gen/streampipes-model';
+
+@Injectable({providedIn: 'root'})
+export class AdapterTemplateService {
+
+  constructor(private dialogService: DialogService) {
+
+  }
+
+  getDialog(configs: StaticPropertyUnion[],
+            appId: string) {
+    return this.dialogService.open(SpAdapterTemplateDialogComponent, {
+      panelType: PanelType.SLIDE_IN_PANEL,
+      title: 'Create configuration template',
+      width: '50vw',
+      data: {
+        'configs': configs,
+        'appId': appId
+      }
+    });
+  }
+
+}
diff --git a/ui/src/app/core-ui/core-ui.module.ts b/ui/src/app/core-ui/core-ui.module.ts
index 22c3f4801..e2330944a 100644
--- a/ui/src/app/core-ui/core-ui.module.ts
+++ b/ui/src/app/core-ui/core-ui.module.ts
@@ -82,6 +82,8 @@ import { MatTreeModule } from '@angular/material/tree';
 import { PlatformServicesModule } from '@streampipes/platform-services';
 import { ImageBarPreviewComponent } from './image/components/image-bar/image-bar-preview/image-bar-preview.component';
 import { SharedUiModule } from '@streampipes/shared-ui';
+import { PipelineElementTemplateConfigComponent } from './pipeline-element-template-config/pipeline-element-template-config.component';
+import { PipelineElementTemplatePipe } from './pipeline-element-template-config/pipeline-element-template.pipe';
 
 @NgModule({
   imports: [
@@ -123,6 +125,8 @@ import { SharedUiModule } from '@streampipes/shared-ui';
     ImageAnnotationsComponent,
     ImageViewerComponent,
     ObjectPermissionDialogComponent,
+    PipelineElementTemplateConfigComponent,
+    PipelineElementTemplatePipe,
     SplitSectionComponent,
     StaticAnyInput,
     StaticPropertyComponent,
@@ -161,6 +165,7 @@ import { SharedUiModule } from '@streampipes/shared-ui';
     ConfigureLabelsComponent,
     ImageComponent,
     ImageLabelingComponent,
+    PipelineElementTemplateConfigComponent,
     SelectLabelComponent,
     StaticAnyInput,
     StaticPropertyComponent,
diff --git a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.html b/ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-config.component.html
similarity index 85%
rename from ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.html
rename to ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-config.component.html
index 97cd14164..2b7bd7f22 100644
--- a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.html
+++ b/ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-config.component.html
@@ -19,19 +19,20 @@
 <div class="p-15">
     <div fxFlex="100" fxLayout="column">
         <h4>Basics</h4>
-        <mat-form-field fxFlex>
+        <mat-form-field fxFlex color="accent">
             <mat-label>Template name</mat-label>
             <input [(ngModel)]="template.templateName" matInput name="templateName" class="sp" required/>
         </mat-form-field>
-        <mat-form-field fxFlex>
+        <mat-form-field fxFlex color="accent">
             <mat-label>Template description</mat-label>
             <input [(ngModel)]="template.templateDescription" matInput name="templateDescription" class="sp"
                    required/>
         </mat-form-field>
-        <mat-checkbox class="sp" color="primary">Make available as pipeline element</mat-checkbox>
+<!--        <mat-checkbox class="sp" color="accent">Make available as pipeline element</mat-checkbox>-->
         <mat-divider class="divider"></mat-divider>
         <h4>Configuration</h4>
-        <div fxLayout="column" *ngFor="let config of cachedPipelineElement.staticProperties"
+        <mat-hint style="margin-bottom: 10px;">(dynamic options cannot be saved and are hidden)</mat-hint>
+        <div fxLayout="column" *ngFor="let config of staticProperties | pipelineElementTemplatePipe"
              class="static-property-panel static-property-panel-border">
             <div fxLayout="row">
                 <div fxFlex="50">
@@ -40,7 +41,7 @@
                 <div fxFlex="50">
                     <div fxLayout="column">
                         <mat-checkbox [checked]="templateConfigs.has(config.internalName)"
-                                      (change)="handleSelection(config)" name="store" class="sp" color="primary">Store
+                                      (change)="handleSelection(config)" name="store" class="sp" color="accent">Store
                             as template
                         </mat-checkbox>
 <!--                        <mat-checkbox *ngIf="templateConfigs.has(config.internalName)"-->
diff --git a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.scss b/ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-config.component.scss
similarity index 95%
copy from ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.scss
copy to ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-config.component.scss
index 2c1a70e95..c232acd88 100644
--- a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.scss
+++ b/ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-config.component.scss
@@ -16,7 +16,7 @@
  *
  */
 
-@import '../../../../scss/sp/sp-dialog.scss';
+@import 'src/scss/sp/sp-dialog';
 
 .divider {
   margin-top: 10px;
diff --git a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.ts b/ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-config.component.ts
similarity index 90%
rename from ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.ts
rename to ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-config.component.ts
index a9e306433..8c27bf476 100644
--- a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.ts
+++ b/ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-config.component.ts
@@ -17,7 +17,6 @@
  */
 
 import { Component, Input, OnInit } from '@angular/core';
-import { InvocablePipelineElementUnion } from '../../model/editor.model';
 import {
   PipelineElementTemplate,
   StaticPropertyUnion
@@ -31,19 +30,22 @@ import { PipelineElementTemplateGenerator } from './pipeline-element-template-ge
 })
 export class PipelineElementTemplateConfigComponent implements OnInit {
 
-  @Input()
-  cachedPipelineElement: InvocablePipelineElementUnion;
-
   @Input()
   template: PipelineElementTemplate;
 
   @Input()
   templateConfigs: Map<string, any>;
 
+  @Input()
+  appId: string;
+
+  @Input()
+  staticProperties: StaticPropertyUnion[];
+
 
   ngOnInit(): void {
-    this.template.basePipelineElementAppId = this.cachedPipelineElement.appId;
-    this.cachedPipelineElement.staticProperties.forEach(sp => {
+    this.template.basePipelineElementAppId = this.appId;
+    this.staticProperties.forEach(sp => {
       this.templateConfigs.set(sp.internalName, this.makeTemplateValue(sp));
     });
   }
diff --git a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-generator.ts b/ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-generator.ts
similarity index 92%
rename from ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-generator.ts
rename to ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-generator.ts
index 72366e380..4d7c8302c 100644
--- a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-generator.ts
+++ b/ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template-generator.ts
@@ -27,7 +27,8 @@ import {
   SecretStaticProperty,
   StaticPropertyAlternative,
   StaticPropertyAlternatives, StaticPropertyGroup,
-  StaticPropertyUnion
+  StaticPropertyUnion,
+  SlideToggleStaticProperty
 } from '@streampipes/platform-services';
 
 export class PipelineElementTemplateGenerator {
@@ -49,6 +50,8 @@ export class PipelineElementTemplateGenerator {
       return this.sp.options.filter(o => o.selected).map(o => o.name);
     } else if (this.sp instanceof CodeInputStaticProperty) {
       return this.sp.value;
+    } else if (this.sp instanceof SlideToggleStaticProperty) {
+      return this.sp.selected;
     } else if (this.sp instanceof CollectionStaticProperty) {
       return {
         members: this.addListEntry(this.sp.members)
@@ -60,9 +63,10 @@ export class PipelineElementTemplateGenerator {
         alternatives: this.addNestedEntry(this.sp.alternatives)
       };
     } else if (this.sp instanceof StaticPropertyAlternative) {
+      const sp = this.sp.staticProperty ? this.addNestedEntry([this.sp.staticProperty]) : {};
       return {
         selected: this.sp.selected,
-        staticProperty: this.addNestedEntry([this.sp.staticProperty])
+        staticProperty: sp
       };
     } else if (this.sp instanceof StaticPropertyGroup) {
       return { staticProperties: this.addNestedEntry(this.sp.staticProperties)};
diff --git a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.scss b/ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template.pipe.ts
similarity index 53%
rename from ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.scss
rename to ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template.pipe.ts
index 2c1a70e95..f44b8aae5 100644
--- a/ui/src/app/editor/components/pipeline-element-template-config/pipeline-element-template-config.component.scss
+++ b/ui/src/app/core-ui/pipeline-element-template-config/pipeline-element-template.pipe.ts
@@ -16,19 +16,22 @@
  *
  */
 
-@import '../../../../scss/sp/sp-dialog.scss';
+import { Injectable, Pipe, PipeTransform } from '@angular/core';
+import {
+  RuntimeResolvableAnyStaticProperty, RuntimeResolvableOneOfStaticProperty, RuntimeResolvableTreeInputStaticProperty,
+  StaticPropertyUnion
+} from '@streampipes/platform-services';
 
-.divider {
-  margin-top: 10px;
-  margin-bottom: 10px;
-}
+@Pipe({name: 'pipelineElementTemplatePipe'})
+@Injectable({providedIn: 'root'})
+export class PipelineElementTemplatePipe implements PipeTransform {
 
-.static-property-panel {
-  padding-left: 10px;
-  margin-bottom: 10px;
-  margin-top: 10px;
-}
+  transform(properties: StaticPropertyUnion[]): StaticPropertyUnion[] {
+
+    return properties.filter(p => (
+      !(p instanceof RuntimeResolvableAnyStaticProperty) &&
+      !(p instanceof RuntimeResolvableOneOfStaticProperty &&
+        !(p instanceof RuntimeResolvableTreeInputStaticProperty))));
+  }
 
-.static-property-panel-border {
-  border-left:5px solid gray;
 }
diff --git a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts
index 15eff2f36..fd3bb56ba 100644
--- a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts
@@ -38,10 +38,10 @@ export class StaticFreeInputComponent
 
   quillModules: any = {
     toolbar: [['bold', 'italic', 'underline', 'strike'],
-      [{ 'header': 1 }, { 'header': 2 }],
-      [{ 'size': ['small', false, 'large', 'huge'] }],
-      [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
-      [{ 'color': [] }, { 'background': [] }]
+      [{'header': 1}, {'header': 2}],
+      [{'size': ['small', false, 'large', 'huge']}],
+      [{'header': [1, 2, 3, 4, 5, 6, false]}],
+      [{'color': []}, {'background': []}]
     ]
   };
 
@@ -50,7 +50,7 @@ export class StaticFreeInputComponent
     ]
   };
 
-  @ViewChild('textEditor', { static: false })
+  @ViewChild('textEditor', {static: false})
   quillEditorComponent: QuillEditorComponent;
 
   constructor(public staticPropertyUtil: StaticPropertyUtilService,
@@ -62,6 +62,7 @@ export class StaticFreeInputComponent
   ngOnInit() {
     this.addValidator(this.staticProperty.value, this.collectValidators());
     this.enableValidators();
+    this.emitUpdate();
   }
 
   collectValidators() {
@@ -83,7 +84,9 @@ export class StaticFreeInputComponent
   }
 
   emitUpdate() {
-    const valid = (this.staticProperty.value !== undefined && this.staticProperty.value !== '');
+    const valid = (this.staticProperty.value !== undefined &&
+      this.staticProperty.value !== '' &&
+      this.staticProperty.value !== null);
     this.updateEmitter.emit(new ConfigurationInfo(this.staticProperty.internalName, valid));
   }
 
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.html b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.html
index 211cce1ea..9e287ba33 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.html
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.html
@@ -32,7 +32,7 @@
             {{option.name}}
         </mat-checkbox>
     </div>
-    <div fxLayout="column" *ngIf="!showOptions && !loading">
+    <div fxLayout="column" *ngIf="!showOptions && !loading" class="mt-10">
         <span>(waiting for input)</span>
     </div>
     <div fxLayout="column" *ngIf="loading">
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.html b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.html
index e58f8adfa..265866cf3 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.html
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.html
@@ -38,7 +38,7 @@
                     </label>
                 </mat-radio-button>
             </div>
-            <div fxLayout="column" *ngIf="!showOptions && !loading">
+            <div fxLayout="column" *ngIf="!showOptions && !loading" class="mt-10">
                 <span>(waiting for input)</span>
             </div>
             <div fxLayout="column" *ngIf="loading">
diff --git a/ui/src/app/core-ui/static-properties/static-secret-input/static-secret-input.component.html b/ui/src/app/core-ui/static-properties/static-secret-input/static-secret-input.component.html
index 54f9ac5d6..4206ab8fe 100644
--- a/ui/src/app/core-ui/static-properties/static-secret-input/static-secret-input.component.html
+++ b/ui/src/app/core-ui/static-properties/static-secret-input/static-secret-input.component.html
@@ -18,7 +18,7 @@
 
 <div [formGroup]="parentForm" id="formWrapper" fxFlex="100" fxLayout="column">
     <div fxFlex="100" fxLayout="row">
-        <mat-form-field fxFlex>
+        <mat-form-field fxFlex color="accent">
             <input type="password"
                    fxFlex
                    formControlName="{{fieldName}}"
@@ -34,4 +34,4 @@
             </mat-error>
         </mat-form-field>
     </div>
-</div>
\ No newline at end of file
+</div>
diff --git a/ui/src/app/editor/dialog/customize/customize.component.html b/ui/src/app/editor/dialog/customize/customize.component.html
index 78c8589a7..9e5e04f3c 100644
--- a/ui/src/app/editor/dialog/customize/customize.component.html
+++ b/ui/src/app/editor/dialog/customize/customize.component.html
@@ -21,9 +21,10 @@
         <div fxFlex="100" fxLayout="column">
             <div style="border-bottom:1px solid #ccc;padding:10px;" fxLayout="row" class="sp-tab-bg">
                 <div fxFlex fxLayoutAlign="start center" *ngIf="availableTemplates && availableTemplates.length > 0">
-                    <mat-form-field class="form-field" floatLabel="never">
+                    <mat-form-field class="form-field" floatLabel="never" color="accent">
                         <mat-label>Use template</mat-label>
-                        <mat-select (selectionChange)="loadTemplate($event)" [(value)]="selectedTemplate">
+                        <mat-select (selectionChange)="loadTemplate($event)"
+                                    [(value)]="selectedTemplate">
                             <mat-option>--</mat-option>
                             <mat-option [value]="template" *ngFor="let template of availableTemplates">
                                 {{template.templateName}}
@@ -72,8 +73,8 @@
                         </div>
                     </div>
                     <div fxLayout="column" *ngIf="templateMode">
-                        <pipeline-element-template-config [cachedPipelineElement]="cachedPipelineElement"
-                        [template]="template" [templateConfigs]="templateConfigs">
+                        <pipeline-element-template-config [staticProperties]="cachedPipelineElement.staticProperties"
+                        [template]="template" [templateConfigs]="templateConfigs" [appId]="cachedPipelineElement.appId">
                         </pipeline-element-template-config>
                     </div>
                 </div>
diff --git a/ui/src/app/editor/dialog/customize/customize.component.ts b/ui/src/app/editor/dialog/customize/customize.component.ts
index 1dfd77244..f042ccfe8 100644
--- a/ui/src/app/editor/dialog/customize/customize.component.ts
+++ b/ui/src/app/editor/dialog/customize/customize.component.ts
@@ -205,9 +205,6 @@ export class CustomizeComponent implements OnInit, AfterViewInit {
           this.cachedPipelineElement = pe as InvocablePipelineElementUnion;
         });
       }
-
     }
   }
-
-
 }
diff --git a/ui/src/app/editor/editor.module.ts b/ui/src/app/editor/editor.module.ts
index 266b0fd9d..92d4ddccd 100644
--- a/ui/src/app/editor/editor.module.ts
+++ b/ui/src/app/editor/editor.module.ts
@@ -57,7 +57,6 @@ import { CustomOutputStrategyComponent } from './components/output-strategy/cust
 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 { 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';
@@ -69,8 +68,8 @@ import { PipelineStyleService } from './services/pipeline-style.service';
 import { PlatformServicesModule } from '@streampipes/platform-services';
 import { PipelineElementIconStandRowComponent } from './components/pipeline-element-icon-stand-row/pipeline-element-icon-stand-row.component';
 import { PipelineElementTypeFilterPipe } from './services/pipeline-element-type-filter.pipe';
-import { PipelineElementNameFilterPipe } from "./services/pipeline-element-name-filter.pipe";
-import { PipelineElementGroupFilterPipe } from "./services/pipeline-element-group-filter.pipe";
+import { PipelineElementNameFilterPipe } from './services/pipeline-element-name-filter.pipe';
+import { PipelineElementGroupFilterPipe } from './services/pipeline-element-group-filter.pipe';
 
 
 @NgModule({
@@ -113,7 +112,6 @@ import { PipelineElementGroupFilterPipe } from "./services/pipeline-element-grou
         PipelineElementOptionsComponent,
         PipelineElementPreviewComponent,
         PipelineElementRecommendationComponent,
-        PipelineElementTemplateConfigComponent,
         PipelineElementTypeFilterPipe,
         PipelineComponent,
         PropertySelectionComponent,