Merge "Bug 571403 - User Interface for Tree Workflow" into develop
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfiguration.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfiguration.java
index de52dff..91b01da 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfiguration.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfiguration.java
@@ -9,6 +9,7 @@
  *
  * Contributors:
  *     Robert Bosch GmbH - initial API and implementation
+ *     Dortmund University of Applied Sciences and Arts
  ********************************************************************************
  */
 package org.eclipse.app4mc.cloud.manager;
@@ -58,7 +59,7 @@
 	}
 	
 	public List<ServiceConfigurationParameter> getParameterList() {
-		return new ArrayList<>(this.parameter);
+		return this.parameter;
 	}
 	
 	public ServiceConfigurationParameter getParameter(String key) {
@@ -69,4 +70,5 @@
 		}
 		return null;
 	}
+	
 }
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNode.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNode.java
index 36e6dc8..94d4143 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNode.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNode.java
@@ -9,6 +9,7 @@
  *
  * Contributors:
  *     Robert Bosch GmbH - initial API and implementation
+ *     Dortmund University of Applied Sciences and Arts
  ********************************************************************************
  */
 package org.eclipse.app4mc.cloud.manager;
@@ -21,6 +22,7 @@
 import org.eclipse.app4mc.cloud.manager.administration.CloudServiceDefinition;
 
 public class ServiceNode {
+	public static final String ROOT_NODE_ID = "root";
 
 	private final String id;
 	private final CloudServiceDefinition service;
@@ -202,4 +204,11 @@
 	public boolean isFailed() {
 		return this.failed;
 	}
+	
+	public String getQualifiedId() {
+		if (this.parentNode == null || this.parentNode.getId().equals(ROOT_NODE_ID)) {
+			return this.id;
+		}
+		return this.parentNode.getQualifiedId() + "." + this.id;
+	}
 }
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNodeProcessingTask.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNodeProcessingTask.java
index a6a17b8..1a785af 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNodeProcessingTask.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNodeProcessingTask.java
@@ -398,7 +398,7 @@
 		ServiceNode sn = node;
 		ArrayList<String> segments = new ArrayList<>();
 		segments.add(getServiceNodeDirName(sn));
-		while (sn.getParentNode() != null && !sn.getParentNode().getId().equals(WorkflowStatus.ROOT_NODE_ID)) {
+		while (sn.getParentNode() != null && !sn.getParentNode().getId().equals(ServiceNode.ROOT_NODE_ID)) {
 			sn = sn.getParentNode();
 			segments.add(0, getServiceNodeDirName(sn));
 		}
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
index 01d4680..40423a7 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
@@ -9,6 +9,7 @@
  *
  * Contributors:
  *     Robert Bosch GmbH - initial API and implementation
+ *     Dortmund University of Applied Sciences and Arts
  ********************************************************************************
  */
 package org.eclipse.app4mc.cloud.manager;
@@ -44,6 +45,7 @@
 import org.springframework.web.bind.annotation.ModelAttribute;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.SessionAttributes;
@@ -214,8 +216,6 @@
 		return "workflow";
 	}
 	
-	// TODO extend selection and removal of services to handle parents
-	
 	@PostMapping("/select/{selected}")
 	public String selectService(
 			@PathVariable(name = "selected") String selected,
@@ -236,24 +236,56 @@
 		return "workflow";
 	}
 	
+	@PostMapping("/select/{parentId}/{selected}")
+	public String selectService(
+			@PathVariable(name = "selected") String selected,
+			@PathVariable(name = "parentId") String parentId,
+			@ModelAttribute WorkflowStatus ws) {
+
+		CloudServiceDefinition csd = this.cloudServiceDefinitions.stream()
+				.filter(sd -> sd.getKey().equals(selected))
+				.findFirst()
+				.orElse(null);
+		
+		if (csd == null) {
+			return "workflow";
+		}
+
+		ws.addSelectedService(parentId, csd, WorkflowStatusHelper.getConfigurationForService(csd));
+		
+		// render the form view
+		return "workflow";
+	}
+	
 	@PostMapping("/remove/{selected}")
 	public String removeService(
 			@PathVariable(name = "selected") String selected,
 			@ModelAttribute WorkflowStatus ws) {
 		
-		CloudServiceDefinition csd = this.cloudServiceDefinitions.stream()
-				.filter(sd -> sd.getKey().equals(selected))
-				.findFirst()
-				.orElse(null);
-
-		if (csd != null) {
-			ws.removeSelectedService(csd);
-		}
+		ws.removeSelectedService(selected);
 		
 		// render the form view
 		return "workflow";
 	}
 	
+	@PostMapping("/remove/{parentId}/{selected}")
+	public String removeService(
+			@PathVariable(name = "parentId") String parentId,
+			@PathVariable(name = "selected") String selected,
+			@ModelAttribute WorkflowStatus ws) {
+		
+		ws.removeSelectedService(parentId, selected);
+
+		return "workflow";
+	}
+	
+	@PostMapping("/removeall")
+	public String removeAllServices(@ModelAttribute WorkflowStatus ws) {
+		ws.clear();
+		// render the form view
+		return "workflow";
+	}
+	
 	@GetMapping("/selectedServices")
 	public String getSelectedServices() {
 		// render the servicesList fragment contained in selectedServices.html
@@ -277,6 +309,54 @@
 		// render the resultList fragment contained in status.html
 		return "status :: resultList";
 	}
+	
+	@GetMapping("/config/{id}")
+	public String getConfiguration(@PathVariable("id") String qualifiedId, Model model, @ModelAttribute WorkflowStatus ws) {
+		ServiceNode node = ws.getServiceByQualifiedId(qualifiedId);
+		model.addAttribute("name", node.getService().getName());
+		model.addAttribute("id", node.getQualifiedId());
+		model.addAttribute("config", node.getServiceConfiguration());
+		model.addAttribute("uuid", ws.getUuid());
+
+		// render the configuration
+		return "configuration :: config";
+	}
+	
+	@PostMapping("/config/{id}")
+	public String saveConfiguration(@PathVariable("id") String qualifiedId, @ModelAttribute ServiceConfiguration config, 
+			Model model, @ModelAttribute WorkflowStatus ws) {
+		
+		// render the configuration
+		ServiceNode node = ws.getServiceByQualifiedId(qualifiedId);
+		ServiceConfiguration nodeConfig = node.getServiceConfiguration();
+		nodeConfig.getParameterList().clear();
+		nodeConfig.getParameterList().addAll(config.getParameterList());
+		
+		model.addAttribute("name", node.getService().getName());
+		model.addAttribute("id", qualifiedId);
+		model.addAttribute("config", node.getServiceConfiguration());
+		
+		return "configuration :: config";
+	}
+	
+	@PutMapping("/config/{id}")
+	public String resetConfiguration(@PathVariable("id") String id, Model model, @ModelAttribute WorkflowStatus ws) {
+		
+		ServiceNode node = ws.getServiceByQualifiedId(id);
+		
+		CloudServiceDefinition csd = this.cloudServiceDefinitions.stream()
+				.filter(sd -> sd.getKey().equals(node.getService().getKey()))
+				.findFirst()
+				.orElse(null);
+		
+		ServiceConfiguration defaultConfig = WorkflowStatusHelper.getConfigurationForService(csd);
+				
+		model.addAttribute("name", node.getService().getName());
+		model.addAttribute("id", id);
+		model.addAttribute("config", defaultConfig);
+		
+		return "configuration :: config";
+	}
 
 	@GetMapping("/{uuid}/files/**")
 	@ResponseBody
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java
index 47d17fb..e02f670 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java
@@ -9,7 +9,7 @@
  *
  * Contributors:
  *     Robert Bosch GmbH - initial API and implementation
- *     Dortmund University of Applied Sciences and Arts - Bug 570871
+ *     Dortmund University of Applied Sciences and Arts
  ********************************************************************************
  */
 package org.eclipse.app4mc.cloud.manager;
@@ -30,12 +30,10 @@
 
 	private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowStatus.class);
 	
-	public static final String ROOT_NODE_ID = "root";
-	
 	private String name;
 	private String uuid;
 	
-	private final ServiceNode rootNode = new ServiceNode(ROOT_NODE_ID);
+	private final ServiceNode rootNode = new ServiceNode(ServiceNode.ROOT_NODE_ID);
 	
 	private final ArrayList<String> messages = new ArrayList<>();
 	private final ArrayList<String> errors = new ArrayList<>();
@@ -82,6 +80,32 @@
 	public List<ServiceNode> getSelectedServices() {
 		return this.rootNode.getChildren();
 	}
+	
+	/**
+	 * @param serviceId	The Id of a given service
+	 * @return The service in the with given id.
+	 */
+	public ServiceNode getServiceByQualifiedId(String qualifiedId) {
+		String[] keys = qualifiedId.split("\\.");
+		ServiceNode parentNode = this.rootNode;
+		ServiceNode node = null;
+		for (int i = 0; i < keys.length; i++) {
+			String key = keys[i];
+			node = parentNode.getChildren().stream()
+					.filter(child -> child.getId().equals(key))
+					.findFirst()
+					.orElse(null);
+			
+			if (node == null) {
+				LOGGER.warn("Could not resolve key '{}' in qualifiedId '{}'", key, qualifiedId);
+				break;
+			}
+			
+			parentNode = node;
+		}
+		
+		return node;
+	}
 
 	/**
 	 * Add a {@link CloudServiceDefinition} and its corresponding
@@ -93,7 +117,10 @@
 	 *                {@link CloudServiceDefinition}. Can be <code>null</code>.
 	 */
 	public void addSelectedService(CloudServiceDefinition service, ServiceConfiguration config) {
-		this.rootNode.addChild(new ServiceNode(service, config));
+		if (this.rootNode.getChildren().stream()
+				.noneMatch(child -> child.getId().equals(service.getKey()))) {
+			this.rootNode.addChild(new ServiceNode(service, config));
+		}
 	}
 	
 	/**
@@ -101,7 +128,7 @@
 	 * {@link ServiceConfiguration} as a child of an already registered service node
 	 * denoted by the given parent key.
 	 * 
-	 * @param parentKey The key to identify an existing service node. A service path
+	 * @param parentId  The qualified Id of the parent - key to identify an existing service node. A service path
 	 *                  can be represented as key by concatenating the keys with a
 	 *                  dot, e.g. migration.validation if validation is a child of
 	 *                  migration. Note: root should not be added as part of the key.
@@ -109,8 +136,8 @@
 	 * @param config    The {@link ServiceConfiguration} for the given
 	 *                  {@link CloudServiceDefinition}. Can be <code>null</code>.
 	 */
-	public void addSelectedService(String parentKey, CloudServiceDefinition service, ServiceConfiguration config) {
-		String[] keys = parentKey.split("\\.");
+	public void addSelectedService(String parentId, CloudServiceDefinition service, ServiceConfiguration config) {
+		String[] keys = parentId.split("\\.");
 		ServiceNode parentNode = this.rootNode;
 		ServiceNode node = null;
 		for (int i = 0; i < keys.length; i++) {
@@ -123,7 +150,14 @@
 			if (i == (keys.length - 1)) {
 				if (node != null) {
 					// we found a node, so simply add the child
-					node.addChild(new ServiceNode(service, config));
+					
+					// If the parent service does not already have that service as a child,
+					// add the service as a child. This helps us to prevent having 
+					// multiple instances of the same service at the same level.
+					if (node.getChildren().stream()
+							.noneMatch(child -> child.getId().equals(service.getKey()))) {
+						node.addChild(new ServiceNode(service, config));
+					}
 				} else if (node == null) {
 					// we did not find the node, so probably a structural node should be added
 					node = new ServiceNode(key);
@@ -155,6 +189,22 @@
 	public void removeSelectedService(CloudServiceDefinition service) {
 		this.rootNode.removeChild(service);
 	}
+	
+	/**
+	 * Removes the given {@link ServiceNode} from the root level of this
+	 * {@link WorkflowStatus}.
+	 * 
+	 * @param serviceKey The key of the {@link CloudServiceDefinition} of the {@link ServiceNode} to remove.
+	 */
+	public void removeSelectedService(String serviceKey) {
+		ServiceNode node = this.rootNode.getChildren().stream()
+							.filter(child -> child.getId().equals(serviceKey))
+							.findFirst()
+							.orElse(null);
+		if (node != null) {
+			this.rootNode.removeChild(node);
+		}
+	}
 
 	/**
 	 * Removes the given {@link CloudServiceDefinition} from the service node
@@ -176,6 +226,39 @@
 	}
 	
 	/**
+	 * Removes the given {@link CloudServiceDefinition} from the service node
+	 * denoted by the given parent ID.
+	 * 
+	 * @param parentId  The qualified ID to identify the existing service node from which the
+	 *                  given service should be removed. A service path can be
+	 *                  represented as key by concatenating the keys with a dot,
+	 *                  e.g. migration.validation if validation is a child of
+	 *                  migration. Note: root should not be added as part of the
+	 *                  key.
+	 * @param key   	The key of the {@link ServiceNode} to remove.
+	 */
+	public void removeSelectedService(String parentId, String serviceKey) {
+		String[] keys = parentId.split("\\.");
+		ServiceNode parentNode = this.rootNode;
+		ServiceNode node = null;
+		for (int i = 0; i < keys.length; i++) {
+			String key = keys[i];
+			node = parentNode.getChildren().stream()
+					.filter(child -> child.getId().equals(key))
+					.findFirst()
+					.orElse(null);
+			
+			if (i == (keys.length - 1)) {
+				if (node != null) {
+					node.removeChild(serviceKey);
+				}
+			}
+			
+			parentNode = node;
+		}
+	}
+	
+	/**
 	 * Removes a service node denoted by the given key.
 	 * 
 	 * @param key The key to identify the service node to remove. A service path can
@@ -331,4 +414,5 @@
 		this.errors.clear();
 		this.results.clear();
 	}
+
 }
diff --git a/manager/src/main/resources/static/css/styles.css b/manager/src/main/resources/static/css/styles.css
index 2d0f1ea..ad2dd8b 100644
--- a/manager/src/main/resources/static/css/styles.css
+++ b/manager/src/main/resources/static/css/styles.css
@@ -1,130 +1,140 @@
-html, body{
-	height:100%;
+html,
+body {
+  height: 100%;
 }
 
-h1{
-	color: black;
+h1 {
+  color: black;
 }
 
-.custom-file-label{
-	border-radius : 40px;
-	-moz-border-radius:40px;
-    -webkit-border-radius:40px;
+.custom-file-label {
+  border-radius: 40px;
+  -moz-border-radius: 40px;
+  -webkit-border-radius: 40px;
 }
 
-.custom-file-label::after{
-	border-radius : 40px;
-	-moz-border-radius:40px;
-    -webkit-border-radius:40px;
+.custom-file-label::after {
+  border-radius: 40px;
+  -moz-border-radius: 40px;
+  -webkit-border-radius: 40px;
 }
 
-.form-control{
-	border-radius : 40px;
-	-moz-border-radius:40px;
-    -webkit-border-radius:40px;
+.form-control {
+  border-radius: 40px;
+  -moz-border-radius: 40px;
+  -webkit-border-radius: 40px;
 }
 
-#wf-fm-group{
-	padding:0;	
+#wf-fm-group {
+  padding: 0;
 }
 
-.hero{
-	height: 100%;
-	margin: 0;
-	padding: 0;
-	background-image: linear-gradient(rgba(220, 220, 220, 0.9) 50%, rgba(220, 220, 220, 0.9) 50%), url("../images/bg-header2.jpg");
-	background-color: #cccccc;
-	background-size: cover;
+.hero {
+  height: 100%;
+  margin: 0;
+  padding: 0;
+  background-image: linear-gradient(
+      rgba(220, 220, 220, 0.9) 50%,
+      rgba(220, 220, 220, 0.9) 50%
+    ),
+    url('../images/bg-header2.jpg');
+  background-color: #cccccc;
+  background-size: cover;
 }
 
-.left-btn, .right-btn{
-  	border-radius: 40px;
-  	padding-left: 30px;
-  	padding-right: 30px;
+.left-btn,
+.right-btn {
+  border-radius: 40px;
+  padding-left: 30px;
+  padding-right: 30px;
 }
-.list-group{
-	border-radius : 40px;
+.list-group {
+  border-radius: 40px;
 }
 
-.banner{
-	margin: 0;
-	padding: 0;
-	background-image: linear-gradient(rgba(220, 220, 220, 0.9) 50%, rgba(220, 220, 220, 0.9) 50%), url("../images/bg-header2.jpg");
-	background-color: #cccccc;
-	background-size: cover;
-	height: 300px;
-	width:100%;
-	z-index: -1;
-	position: absolute;
-	top: 0;
-    right: 0;
+.banner {
+  margin: 0;
+  padding: 0;
+  background-image: linear-gradient(
+      rgba(220, 220, 220, 0.9) 50%,
+      rgba(220, 220, 220, 0.9) 50%
+    ),
+    url('../images/bg-header2.jpg');
+  background-color: #cccccc;
+  background-size: cover;
+  height: 300px;
+  width: 100%;
+  z-index: -1;
+  position: absolute;
+  top: 0;
+  right: 0;
 }
 
-.list-group-item{
-	border-radius : 40px;
-	-moz-border-radius:40px;
-    -webkit-border-radius:40px;
-    padding-top: 5px;
-    padding-bottom: 5px;
-    padding-right: 5px;
+.list-group-item {
+  border-radius: 40px;
+  -moz-border-radius: 40px;
+  -webkit-border-radius: 40px;
+  padding-top: 5px;
+  padding-bottom: 5px;
+  padding-right: 5px;
 }
 
-.btn-rmv{
-	background-color: Black;
-    border: none;
-    color: white;
-    padding: 10px 14px;
-    font-size: 16px;
-    cursor: pointer;
-    border-radius : 50%;
+.btn-rmv {
+  background-color: Black;
+  border: none;
+  color: white;
+  padding: 10px 14px;
+  font-size: 16px;
+  cursor: pointer;
+  border-radius: 50%;
 }
 
-.navcon{
-	margin: 0;
-	padding: 0;
+.navcon {
+  margin: 0;
+  padding: 0;
 }
 
-.nav-item{
-	margin-left: 30px;
+.nav-item {
+  margin-left: 30px;
 }
 
-.nav-brand{
-	width: 150px;
-	display: block;
+.nav-brand {
+  width: 150px;
+  display: block;
 }
 
 .plist {
   margin: auto 0;
 }
 
-.fm-ctrl-left{
-	border-radius : 40px 0px 0px 40px;
-	-moz-border-radius:40px 0px 0px 40px;
-    -webkit-border-radius:40px 0px 0px 40px;
+.fm-ctrl-left {
+  border-radius: 40px 0px 0px 40px;
+  -moz-border-radius: 40px 0px 0px 40px;
+  -webkit-border-radius: 40px 0px 0px 40px;
 }
 
-.fm-ctrl-center{
-	border-radius : 0;
-	-moz-border-radius:0;
-    -webkit-border-radius:0;
+.fm-ctrl-center {
+  border-radius: 0;
+  -moz-border-radius: 0;
+  -webkit-border-radius: 0;
 }
 
-.fm-ctrl-right{
-	border-radius : 0px 40px 40px 0px;
-	-moz-border-radius:0px 40px 40px 0px;
-    -webkit-border-radius:0px 40px 40px 0px;
+.fm-ctrl-right {
+  border-radius: 0px 40px 40px 0px;
+  -moz-border-radius: 0px 40px 40px 0px;
+  -webkit-border-radius: 0px 40px 40px 0px;
 }
 
-.btn-save{
-    border: none;
-    color: white;
-    padding: 10px 14px;
-    font-size: 16px;
-    cursor: pointer;
+.btn-save {
+  border: none;
+  color: white;
+  padding: 10px 14px;
+  font-size: 16px;
+  cursor: pointer;
 }
 
-.half-btn{
-	width: 100px
+.half-btn {
+  width: 100px;
 }
 
 /* Scroll Bar */
@@ -133,16 +143,16 @@
 }
 
 ::-webkit-scrollbar-track {
-  background: #f1f1f1; 
+  background: #f1f1f1;
 }
- 
+
 ::-webkit-scrollbar-thumb {
   background: #888;
   border-radius: 5px;
 }
 
 ::-webkit-scrollbar-thumb:hover {
-  background: #555; 
+  background: #555;
 }
 
 .disabled_service {
@@ -153,5 +163,218 @@
 .flash {
   color: #007bff;
   font-weight: bold;
-  margin-bottom: 1.5rem!important;
+  margin-bottom: 1.5rem !important;
 }
+
+.tree,
+.tree ul,
+.tree li {
+  position: relative;
+  padding-bottom: 10px;
+}
+
+.stem {
+  padding: 0 0 0 8px;
+  font-size: 24px;
+  margin-right: 6px;
+}
+
+.stem::before {
+  position: relative;
+  content: '\1F4C4';
+  top: 2px;
+  margin-right: 6px;
+}
+
+.tree ul {
+  list-style: none;
+  padding-left: 32px;
+}
+
+.tree li::before,
+.tree li::after {
+  content: '';
+  position: absolute;
+  left: -12px;
+}
+
+.tree li::before {
+  border-top: 3px solid #000;
+  top: 9px;
+  width: 16px;
+  height: 0;
+}
+
+.tree li::after {
+  border-left: 4px solid #000;
+  height: 100%;
+  width: 0px;
+  top: 2px;
+}
+
+.tree ul > li:last-child::after {
+  height: 8px;
+}
+
+.tree ul > li:last-child {
+  padding-bottom: 0;
+}
+
+/* Style the caret/arrow */
+.caret-down {
+  cursor: pointer;
+  user-select: none; /* Prevent text selection */
+}
+
+/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
+.caret-down::before {
+  content: '\25B6';
+  color: black;
+  display: inline-block;
+  margin-right: 6px;
+  margin-left: 6px;
+  transform: rotate(90deg);
+}
+
+.caret::before {
+  transform: rotate(0deg);
+}
+
+.space::before {
+  content: '\2000';
+  color: black;
+  display: inline-block;
+  margin-right: 6px;
+  margin-left: 6px;
+}
+
+.nested {
+  display: none;
+}
+
+.active {
+  display: block;
+}
+
+/* The Modal (background) */
+.modal {
+  display: none;
+  position: fixed;
+  z-index: 4;
+  padding-top: 20vh;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+  background-color: rgb(0, 0, 0);
+  background-color: rgba(0, 0, 0, 0.4);
+}
+
+/* Modal Content */
+.modal-content {
+  background-color: #fefefe;
+  margin: auto;
+  padding: 20px;
+  border: 1px solid #888;
+  border-radius: 10px;
+  width: 60%;
+  max-height: 60vh;
+}
+
+/* The Close Button */
+.modalClose {
+  color: #aaaaaa;
+  float: right;
+  font-size: 28px;
+  font-weight: bold;
+}
+
+.modalClose:hover,
+.modalClose:focus {
+  color: #000;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.servicemodalgrid {
+  display: grid;
+  grid-template-columns: 20% auto;
+  grid-column-gap: 10px;
+  grid-row-gap: 10px;
+}
+
+.action {
+  cursor: pointer;
+}
+
+.action-lg {
+  cursor: pointer;
+  font-size: 20px;
+}
+
+.scroll{
+	overflow-y: auto;
+	padding: 0 25px;
+}
+
+.scroll::-webkit-scrollbar-thumb,
+.scroll::-webkit-scrollbar-track {
+	visibility: hidden;
+}
+
+.scroll:hover::-webkit-scrollbar-thumb,
+.scroll:hover::-webkit-scrollbar-track {
+	visibility: visible;
+}
+
+.tag {
+  border-radius: 15px;
+}
+
+.tagText {
+  font-size: 12px;
+}
+
+#configurationParameterOptions {
+  height: 50px;
+  border-radius: 15px;
+  overflow-x: auto;
+}
+
+.dropdown {
+  position: relative;
+  display: inline-block;
+}
+
+#dropdown-content {
+  display: none;
+  position: absolute;
+  background-color: #f1f1f1;
+  min-width: 160px;
+  box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
+  z-index: 1;
+}
+
+#dropdown-content a {
+  color: black;
+  padding: 12px 16px;
+  text-decoration: none;
+  display: block;
+}
+
+#dropdown-content a:hover {
+  background-color: #ddd;
+}
+
+.disabled_control {
+  pointer-events: none;
+}
+
+.index-btn{
+	width: 200px;
+}
+
+.config-desc{
+	font-size: 10px;
+}
\ No newline at end of file
diff --git a/manager/src/main/resources/static/js/workflow.js b/manager/src/main/resources/static/js/workflow.js
index 7f11b7c..40f155a 100644
--- a/manager/src/main/resources/static/js/workflow.js
+++ b/manager/src/main/resources/static/js/workflow.js
@@ -29,6 +29,22 @@
 	});
 }
 
+function updateSelectedServicesWithParent(service) {
+	let parentId = $('#pserviceId').val();
+	if (parentId === '--'){
+		updateSelectedServices(service);
+	} else {
+		$.ajax({
+			type: 'POST',
+			url: '/select/' + parentId + '/' + service,
+			success: function(result) {
+				$('#selectedServicesBlock').load('/selectedServices');
+				$('[data-toggle="tooltip"]').tooltip();
+			}
+		});
+	}
+}
+
 function selectServiceProfile(profile) {
 	$.ajax({
 		type: 'POST',
@@ -49,6 +65,161 @@
 	});
 }
 
+function removeService() {
+	let parentId = $('#parentId').val();
+	let serviceKey = $('#serviceKey').val();
+	
+	if (parentId === '--'){
+		removeSelectedServices(serviceKey);
+		
+	}else if (parentId === '' && serviceKey === ''){
+		$.ajax({
+			type: 'POST',
+			url: '/removeall',
+			success: function(result) {
+				$('#selectedServicesBlock').load('/selectedServices');
+			}
+		});
+		
+	}else{
+		$.ajax({
+			type: 'POST',
+			url: '/remove/' + parentId + '/' + serviceKey,
+			success: function(result) {
+				$('#selectedServicesBlock').load('/selectedServices');
+			}
+		});
+	}
+}
+
+function toggleBranch(element){
+    element.parentElement.querySelector(".nested").classList.toggle("active");
+    element.classList.toggle("caret");
+}
+
+function openSelectModal(parentService, parentServiceId){
+    $('#serviceModal').css("display", "block");
+    $('#pservice').val(parentService);
+    $('#pserviceId').val(parentServiceId);
+}
+
+function openRemoveAllServiceModal() {
+    $('#removeserviceModal').css("display", "block");
+    const header = "Delete All Services";
+    const text = "Are you sure you want to delete all services?";
+
+    $('#removeServiceHeader').text(header);
+    $('#removeServiceTxt').text(text);
+    $('#removeModalGrid').css("display", "none");
+    $('#parentId').val("")
+    $('#serviceKey').val("")
+}
+
+function openRemoveServiceModal(parentId, serviceKey) {
+    $('#removeserviceModal').css("display", "block");
+    const header = "Delete a service";
+    const text = "Are you sure you want to delete this service and all its children?";
+
+    $('#removeServiceHeader').text(header);
+    $('#removeServiceTxt').text(text);
+    $('#removeModalGrid').css("display", "grid");
+    $('#parentId').val(parentId)
+    $('#serviceKey').val(serviceKey)
+}
+
+function openConfigModal(serviceId) {
+	$.ajax({
+		url: '/config/' + serviceId,
+		success: function(result) {
+			$("#configModalHolder").html(result);
+			$('#configurationModal').css("display", "block");
+		}
+	});
+}
+
+function closeServiceModal(){
+    $('#serviceModal').hide();
+}
+
+function closeRemoveServiceModal(){
+    $('#removeserviceModal').hide();
+}
+
+function closeConfigModal(){
+    $('#configurationModal').hide();
+}
+
+function saveConfiguration(){
+    $('#configForm').ajaxSubmit({
+    	success: function(result) {
+			$('#configurationModal').hide();
+		}
+    });
+}
+
+function resetConfiguration(serviceId){
+	$.ajax({
+		type: 'PUT',
+		url: '/config/' + serviceId,
+		success: function(result) {
+			$("#configModalHolder").html(result);
+			$('#configurationModal').css("display", "block");
+		}
+	});
+}
+
+function displayServiceProfileOptions() {
+    $('#dropdown-content').css("display", "block");
+    event.stopPropagation();
+}
+
+function addConfigurationParameterOption(element) {
+	$('#dropdown-content').hide();
+    const input = document.getElementById('configurationParameterOptionsSelected');
+    if(input.value.includes(element.text)) return;
+
+    if (input.value !== "") {
+        input.value = input.value.concat(',', element.text)
+    } else {
+        input.value = element.text;
+    }
+    $('#configurationParameterOptionsSelected').trigger("change");
+}
+
+function onConfigurationParameterOptionChanged(element) {
+    const valArray = element.value.split(',');
+    const div = document.getElementById('configurationParameterOptions');
+    div.innerHTML = '';
+    for (let index = 0; index < valArray.length; index++) {
+        if (valArray[index] !== '') {
+			const html = 
+			'<div class="d-flex bg-info px-2 py-1 mr-1 align-middle tag">\
+				<p class="mr-1 my-auto pr-1 border-right border-secondary text-light text-nowrap tagText">' + valArray[index] + '</p>\
+				<a class="my-auto text-light tagClose" onclick="removeConfigurationParameterOption(this)">\
+					<i class="fas fa-times-circle"></i>\
+				</a>\
+			</div>';
+
+            div.innerHTML += html;
+        }
+    }
+}
+
+function removeConfigurationParameterOption(element) {
+    const value = element.previousElementSibling.innerText;
+    const input = document.getElementById('configurationParameterOptionsSelected');
+    let valArray = input.value.split(',');
+    valArray = valArray.filter(val => val !== value);
+    input.value = valArray.join();
+    $('#configurationParameterOptionsSelected').trigger("change");
+    
+    event.stopPropagation()
+}
+
+function onConfigurationBackgroundClick() {
+	$('#dropdown-content').hide();
+}
+
 function loadFragments() {
 	$('#selectedServicesBlock').load('/selectedServices');
 	$('#messagesBlock').load('/messages');
diff --git a/manager/src/main/resources/templates/configuration.html b/manager/src/main/resources/templates/configuration.html
new file mode 100644
index 0000000..590a791
--- /dev/null
+++ b/manager/src/main/resources/templates/configuration.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html xmlns:th="https://www.thymeleaf.org">
+
+<head>
+</head>
+
+<body>
+	<div id="configurationModal" onclick="onConfigurationBackgroundClick()" class="modal" th:fragment="config">
+        <!-- Modal content -->
+        <form id="configForm" action="#" th:action="@{/config/__${id}__}" method="post" th:object="${config}" class="modal-content">
+            <div class="d-flex justify-content-end">
+            	<span class="modalClose" id="closeConfigModal" onclick="closeConfigModal()">&times;</span>
+            </div>
+            <h3 class="border-bottom mb-2">Configuration...</h3>
+    
+    		<div class="scroll">
+               <h5 th:text="${name + ' Service'}">Service</h5>
+               <div th:if="${config.serviceDescription != '' and config.serviceDescription != null}" class="d-flex flex-row mb-3 ml-3">
+	               <i class="fas fa-info-circle text-secondary my-auto"></i>
+	               <p th:text="${config.serviceDescription}" class="font-italic config-desc ml-2 my-auto text-secondary">Description</p>
+               </div>
+               <div class="servicemodalgrid mb-1">
+                   <label for="parentId">Service Id:</label>
+                   <input class="form-control" type="text" id="parentId" th:value="${id}" readonly>
+               </div>
+               
+               <div class="mb-3">
+					<div th:each="parameter, parameterStatus : ${config.parameterList}">
+						<!-- Bind all unused variables of the configuration for a complete
+						representation of the configuration in the backend. But make them
+						invisible in the front end. -->
+						<div class="d-none">
+							<input type="text" th:field=*{parameterList[__${parameterStatus.index}__].cardinality}/>
+							<input type="text" th:field=*{parameterList[__${parameterStatus.index}__].type}/>
+							<input type="text" th:field=*{parameterList[__${parameterStatus.index}__].name}/>
+							<input type="text" th:field=*{parameterList[__${parameterStatus.index}__].key}/>
+							<input type="text" th:field=*{parameterList[__${parameterStatus.index}__].mandatory}/>
+							<input type="text" th:field=*{parameterList[__${parameterStatus.index}__].managerParameter}/>
+							<div th:each="pv, pvStatus : ${parameter.possibleValues}">
+								<input type="text" th:field=*{parameterList[__${parameterStatus.index}__].possibleValues[__${pvStatus.index}__]}/>
+							</div>
+						</div>
+						
+						<!-- single non-boolean value -->
+						<div class="servicemodalgrid my-1" th:if="${parameter.cardinality == 'single' and parameter.type != 'boolean' and parameter.possibleValues.isEmpty()}">
+							<label class="form-check-label" th:text="${parameter.name + ':'}">Label</label>
+							<input 
+								class="form-control" 
+								type="text" 
+								th:field="*{parameterList[__${parameterStatus.index}__].value}"
+								th:disabled="${uuid != null}"
+								th:title="*{parameterList[__${parameterStatus.index}__].description}"
+								data-toggle="tooltip"/>
+						</div>
+						
+						<!-- single boolean value -->
+						<div th:if="${parameter.cardinality == 'single' and parameter.type == 'boolean'}" class="form-check">
+							<input
+								class="form-check-input"
+								type="checkbox"
+								th:field="*{parameterList[__${parameterStatus.index}__].value}"
+								th:value="true"
+								th:disabled="${uuid != null}">
+							<label 
+								class="form-check-label" 
+								th:text="${parameter.name}" 
+								th:for="${parameter.key}"
+								th:title="*{parameterList[__${parameterStatus.index}__].description}"
+								data-toggle="tooltip">Label</label>
+						</div>
+						
+						<!-- multiple possible values + cardinality multiple = tags -->
+						<div class="servicemodalgrid my-1" th:if="${parameter.cardinality == 'multiple' and parameter.possibleValues.size() > 1}">
+							<label 
+								th:text="${parameter.name + ':'}"
+								th:title="*{parameterList[__${parameterStatus.index}__].description}"
+								data-toggle="tooltip">Label</label>
+							
+							<!-- The visible input for the multiple values. The binding to the configuration object is not done here. -->
+							<div class="dropdown w-100">
+						        <div id="configurationParameterOptions" 
+						        	class="d-flex flex-wrap border p-2"
+						        	th:classappend="${uuid != null} ? 'disabled_service disabled_control' : ''"
+						        	onclick="displayServiceProfileOptions()">
+						        	<th:block th:each="pv : ${parameter.possibleValues}">
+							            <div th:if="${parameter.value != null && parameter.value.contains(pv)}" class="d-flex bg-info px-2 py-1 mr-1 align-middle tag">
+							                <p class="mr-1 my-auto pr-1 border-right border-secondary text-light text-nowrap tagText" th:text="${pv}">Text</p>
+							                <a class="my-auto text-light tagClose"
+							                	th:classappend="${uuid != null} ? disabled_control : ''"
+							                	onclick="removeConfigurationParameterOption(this)">
+							                	<i class="fas fa-times-circle"></i>
+							                </a>
+							            </div>
+						            </th:block>
+						        </div>
+						        <div id="dropdown-content">
+						        	<div th:each="pv : ${parameter.possibleValues}">
+						            	<a th:text="${pv}" onclick="addConfigurationParameterOption(this)">Text</a>
+						            </div>
+						        </div>
+						    </div>
+						    
+						    <!-- Bind the multiple values to this hidden input -->
+    						<input
+    							th:field="*{parameterList[__${parameterStatus.index}__].value}"
+    							class="d-none"
+    							id="configurationParameterOptionsSelected"
+    							onchange="onConfigurationParameterOptionChanged(this)"/>
+						</div>
+						
+						<!-- multiple possible values + cardinality single = combobox -->
+						<div class="servicemodalgrid my-1" th:if="${parameter.cardinality == 'single' and parameter.possibleValues.size() > 1}">
+							<label th:text="${parameter.name + ':'}">Label</label>
+							<select
+								class="custom-select"
+								th:field="*{parameterList[__${parameterStatus.index}__].value}"
+								th:disabled="${uuid != null}"
+								th:title="*{parameterList[__${parameterStatus.index}__].description}"
+								data-toggle="tooltip">
+								<option value=""></option>
+							    <option 
+							    	th:each="pv : ${parameter.possibleValues}"  
+							    	th:text="${pv}"
+							    	th:value="${pv}">
+							    </option>
+							</select>
+						</div>
+					</div>
+				</div>
+               
+               <div class="d-flex justify-content-center my-4"  th:if="${uuid == null}">
+               		<a class="btn btn-secondary half-btn fm-ctrl-left" th:attr="onclick=|resetConfiguration('${id}')|">Reset</a>
+					<a class="btn btn-primary half-btn fm-ctrl-right" onclick="saveConfiguration()">Save</a>
+               </div>
+             </div>
+        </form>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/manager/src/main/resources/templates/index.html b/manager/src/main/resources/templates/index.html
index 9293d64..ca7ce04 100644
--- a/manager/src/main/resources/templates/index.html
+++ b/manager/src/main/resources/templates/index.html
@@ -22,10 +22,10 @@
 			</div>
 			<div class="row mt-2">
 				<div class="col text-right pr-1">
-					<a class="btn btn-primary mt-2 fm-ctrl-left" href="/workflow">Start Workflow</a>
+					<a class="btn btn-light text-primary mt-2 fm-ctrl-left index-btn" href="/workflowConfig">Configure Workflow</a>
 				</div>
 				<div class="col pl-1">
-					<a class="btn btn-primary mt-2 fm-ctrl-right" href="/workflowConfig">Configure Workflow</a>
+					<a class="btn btn-primary mt-2 fm-ctrl-right index-btn" href="/workflow">Start Workflow</a>
 				</div>
 			</div>
 		</div>
diff --git a/manager/src/main/resources/templates/selectedServices.html b/manager/src/main/resources/templates/selectedServices.html
index 26b0114..afbe15c 100644
--- a/manager/src/main/resources/templates/selectedServices.html
+++ b/manager/src/main/resources/templates/selectedServices.html
@@ -6,102 +6,96 @@
 
 <body>
     <div th:fragment="servicesList" id="selectedServices" th:object="${workflowStatus}">
-		<ul class="list-group"> 
-			<li th:each="selected : *{selectedServices}" class="list-group-item d-flex justify-content-between" th:classappend="*{uuid != null} ? disabled_service : ''">
-				<p 
-					class="p-0 flex-grow-1 plist" 
-					th:text="${selected.service.name}" 
-					th:title="${selected.service.description}"
-					data-toggle="tooltip"
-					th:classappend="*{uuid != null} ? disabled_service : ''">Service</p> 
-				<a 
-					th:if="*{uuid == null}"
-					th:attr="onclick=|removeSelectedServices('${selected.service.key}')|" 
-					class="btn btn-primary btn-sm btn-rmv">
-					<i class="fas fa-times"></i>
-				</a>
-			</li>
-		</ul>
-		<select 
-			name="services" 
-			id="services" 
-			onchange="updateSelectedServices(this.value)" 
-			class="form-control"
-			th:if="*{uuid == null}">
-			<option></option>
-		    <option 
-		    	th:each="service : ${cloudServiceDefinitions}" 
-		    	th:text="${service.name}" 
-		    	th:value="${service.key}">
-		    </option>
-		</select><br>
-		
-		<div th:if="not *{selectedServices.isEmpty()}">
-			<div th:each="node, nodeStatus : *{selectedServices}" class="mb-3">
-				<h4 th:text="${node.service.name + ' Configuration'}">Config</h4>
-				<div th:text="${node.serviceConfiguration.serviceDescription}" class="font-italic mb-1">Description</div>
-				<div th:each="parameter, parameterStatus : ${node.serviceConfiguration.parameterList}">
-					<!-- single non-boolean value -->
-					<div th:if="${parameter.cardinality == 'single' and parameter.type != 'boolean' and parameter.possibleValues.isEmpty()}">
-						<label class="form-check-label" th:text="${parameter.name}">Label</label>
-						<input 
-							class="form-control mb-1" 
-							type="text" 
-							th:field="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].value}"
-							th:disabled="*{uuid != null}"
-					    	th:title="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].description}"
-							data-toggle="tooltip"/>
-					</div>
-					<!-- single boolean value -->
-					<div th:if="${parameter.cardinality == 'single' and parameter.type == 'boolean'}" class="form-check">
-						<input
-							class="form-check-input mb-1"
-							type="checkbox"
-							th:field="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].value}"
-							th:value="true"
-							th:disabled="*{uuid != null}">
-						<label
-							class="form-check-label"
-							th:text="${parameter.name}"
-							th:for="${parameter.key}"
-					    	th:title="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].description}"
-							data-toggle="tooltip">Label</label>
-					</div>
-					<!-- multiple possible values + cardinality multiple = checkboxes -->
-					<div th:if="${parameter.cardinality == 'multiple' and parameter.possibleValues.size() > 1}">
-						<label
-							th:text="${parameter.name}"th:title="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].description}"
-							data-toggle="tooltip">Label</label>
-						<div th:each="pv : ${parameter.possibleValues}" class="form-check">
-							<input
-								type="checkbox"
-								th:field="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].value}"
-								th:value="${pv}"
-								th:disabled="*{uuid != null}">
-							<label class="form-check-label" th:text="${pv}">Value</label>
-						</div>
-					</div>
-					<!-- multiple possible values + cardinality single = combobox -->
-					<div th:if="${parameter.cardinality == 'single' and parameter.possibleValues.size() > 1}">
-						<label th:text="${parameter.name}">Label</label>
-						<select
-							class="form-control mb-1"
-							th:field="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].value}"
-							th:disabled="*{uuid != null}"
-							th:title="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].description}"
-							data-toggle="tooltip">
-							<option value=""></option>
+		<div class="tree">
+	        <span class="stem">Input</span>
+	        <i class="fas fa-plus-circle text-primary action-lg"
+	        	onclick="openSelectModal('--', '--')"
+	        	title="Add service"
+	        	th:if="*{uuid == null}"></i>
+	        <i class="fas fa-times-circle fa-2x text-black action-lg"
+	        	title="Delete All Services"
+	        	onclick="openRemoveAllServiceModal()"
+	        	th:if="not *{selectedServices.isEmpty()} and *{uuid == null}"></i>
+	        <ul>
+	            <li th:each="selected : *{selectedServices}" th:classappend="*{uuid != null} ? disabled_service : ''">
+	                <span 
+	                	th:class="${selected.children.isEmpty()}? space : caret-down"
+	                	th:text="${selected.isStructuralNode()? '' : selected.service.name}"
+						th:title="${selected.isStructuralNode()? '' : selected.service.description}"
+						onclick="toggleBranch(this)"
+						data-toggle="tooltip"
+						th:classappend="*{uuid != null} ? disabled_service : ''">Service 1</span>
+	                <i class="fas fa-plus-circle text-primary action"
+	                	th:attr="onclick=|openSelectModal('${selected.isStructuralNode()? '' : selected.service.key}', '${selected.getQualifiedId()}')|"
+	                	title="Add Child Service"
+	                	th:if="*{uuid == null}"></i>
+	                <i class="fas fa-times-circle text-black action"
+	                	th:attr="onclick=|openRemoveServiceModal('--', '${selected.isStructuralNode()? '' : selected.service.key}')|"
+	                	title="Delete Service"
+	                	th:if="*{uuid == null}"></i>
+	                <i class="fas fa-cogs text-secondary action"
+	                	th:attr="onclick=|openConfigModal('${selected.getQualifiedId()}')|"
+	                	title="Service Configuration"></i>
+	                
+	                <section th:include="serviceChildren :: children" th:with="children=${selected.children}, parent=${selected}"></section>
+	            </li>
+	        </ul>
+	    </div>
+	 
+		<!-- The modal view for selecting child services -->
+		<div id="serviceModal" class="modal">
+	        <!-- Modal content -->
+	        <div class="modal-content">
+	            <div class="d-flex justify-content-end">
+	            	<span class="modalClose" id="closemodal" onclick="closeServiceModal()">&times;</span>
+	            </div>
+	            <h3 class="border-bottom">Add service...</h3>
+	            <div class="scroll">
+		            <div class="servicemodalgrid">
+	                    <label for="pservice">Parent Service:</label>
+	                    <input class="form-control" type="text" id="pservice" readonly>
+	                    <label for="pserviceId">Parent Service Id:</label>
+	                    <input class="form-control" type="text" id="pserviceId" readonly>
+	                    <label for="serviceSelect">Service:</label>
+	                    <select id="serviceSelect" 
+	                    	class="form-control"
+	                    	th:attr="onchange=|updateSelectedServicesWithParent(this.value)|">
+	                        <option></option>
 						    <option 
-						    	th:each="pv : ${parameter.possibleValues}"  
-						    	th:text="${pv}"
-						    	th:value="${pv}">
+						    	th:each="service : ${cloudServiceDefinitions}" 
+						    	th:text="${service.name}" 
+						    	th:value="${service.key}">
 						    </option>
-						</select>
-					</div>
-				</div>
-			</div>
-		</div>
-		
+	                    </select>
+		            </div>
+	            </div>
+	        </div>
+	    </div>
+	    
+	    <!-- Remove Service Modal -->
+	    <div id="removeserviceModal" class="modal">
+	        <!-- Modal content -->
+	        <div class="modal-content">
+	            <div class="d-flex justify-content-end">
+	            	<span class="modalClose" id="closeRemoveModal" onclick="closeRemoveServiceModal()">&times;</span>
+	            </div>
+	            <h3 id="removeServiceHeader" class="border-bottom mb-2">Delete a service...</h3>
+	    		<div class="scroll">
+		            <p id="removeServiceTxt">Are you sure you want to delete this service and all its children?</p>
+		            <div class="servicemodalgrid" id="removeModalGrid">
+		                <label for="parentId">Parent Service Id:</label>
+		                <input class="form-control" type="text" id="parentId" readonly>
+		                <label for="serviceKey">Service Key:</label>
+		                <input class="form-control" type="text" id="serviceKey" readonly>
+		            </div>
+		            <div class="d-flex justify-content-center my-4">
+		                <a class="btn btn-primary fm-ctrl-left half-btn" onclick="removeService()">Yes</a>
+		                <a class="btn btn-secondary fm-ctrl-right half-btn" onclick="closeRemoveServiceModal()">No</a>
+		            </div>
+	            </div>
+	        </div>
+	    </div>
+	    
     </div>
 </body>
 </html>
diff --git a/manager/src/main/resources/templates/serviceChildren.html b/manager/src/main/resources/templates/serviceChildren.html
new file mode 100644
index 0000000..b818ec0
--- /dev/null
+++ b/manager/src/main/resources/templates/serviceChildren.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html xmlns:th="https://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+
+<head>
+</head>
+
+<body>
+	<div th:fragment="children">
+		<ul class="nested active">
+            <li th:each="child : ${children}">
+                <span 
+                	th:class="${child.children.isEmpty()}? space : caret-down"
+                	th:text="${child.isStructuralNode()? '' : child.service.name}"
+					th:title="${child.isStructuralNode()? '' : child.service.description}"
+					onclick="toggleBranch(this)"
+					th:classappend="*{uuid != null} ? disabled_service : ''">Child Service</span>
+                <i class="fas fa-plus-circle text-primary action"
+                	th:attr="onclick=|openSelectModal('${child.isStructuralNode()? '' : child.service.key}', '${child.getQualifiedId()}')|"
+                	title="Add Child Service"
+                	th:if="*{uuid == null}"></i>
+                <i class="fas fa-times-circle text-black action"
+                	title="Delete Service"
+                	th:attr="onclick=|openRemoveServiceModal('${child.parentNode.getQualifiedId()}', '${child.isStructuralNode()? '' : child.service.key}')|"
+                	th:if="*{uuid == null}"></i>
+                <i class="fas fa-cogs text-secondary action"
+                	th:attr="onclick=|openConfigModal('${child.getQualifiedId()}')|"
+	                title="Service Configuration"
+	                th:if="${not child.isStructuralNode()}"></i>
+                
+                <section th:include="serviceChildren :: children" th:with="children=${child.children}, parent=${child}"></section>
+            </li>
+        </ul>
+	</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/manager/src/main/resources/templates/workflow.html b/manager/src/main/resources/templates/workflow.html
index 1ce4f39..7ad11dd 100644
--- a/manager/src/main/resources/templates/workflow.html
+++ b/manager/src/main/resources/templates/workflow.html
@@ -80,6 +80,8 @@
 
 		<div id="resultsBlock"></div>
 
+		<!-- Configuration Modal -->
+	    <div id="configModalHolder"></div>
 	</div>
 </body>
 </html>
\ No newline at end of file