Added tooltips on selected services

Change-Id: I8558301bd178cdc53000d38bdf24c764d1f1af4f
Signed-off-by: Dirk Fauth <Dirk.Fauth@de.bosch.com>
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ProcessLog.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ProcessLog.java
index ba97da5..30c2412 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ProcessLog.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ProcessLog.java
@@ -24,6 +24,7 @@
 	
 	private Action action;
 	private String message;
+	private String uuid;
 
 	public ProcessLog() {
 	}
@@ -53,4 +54,12 @@
 		this.message = message;
 	}
 	
+	public ProcessLog withUuid(String uuid) {
+		this.uuid = uuid;
+		return this;
+	}
+	
+	public String getUuid() {
+		return this.uuid;
+	}
 }
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 d183bcc..d5f0040 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
@@ -66,14 +66,15 @@
 	private final StorageService storageService;
 
 	private ExecutorService executor = Executors.newCachedThreadPool();
-
-	private HashMap<String, WorkflowStatus> workflowStatusMap = new HashMap<>();
 	
 	private DateFormat formatter = DateFormat.getDateTimeInstance(
             DateFormat.SHORT, 
             DateFormat.SHORT, 
             Locale.ENGLISH);
 	
+	@javax.annotation.Resource(name = "workflowStatusMap")
+	private HashMap<String, WorkflowStatus> workflowStatusMap;
+	
 	@javax.annotation.Resource(name = "cloudServiceDefinitions")
 	List<CloudServiceDefinition> cloudServiceDefinitions;
 
@@ -87,14 +88,17 @@
 
 	@GetMapping("/workflow")
 	public String workflow(
-			Model model,
+			Model model, 
+			@ModelAttribute WorkflowStatus workflowStatus,
 			@RequestParam(name = "uuid", required = false) String uuid) {
 		
 		if (!StringUtils.isEmpty(uuid)) {
 			
 			WorkflowStatus existing = this.workflowStatusMap.get(uuid);
 			if (existing == null) {
-				existing = WorkflowStatusHelper.loadWorkflowStatus(storageService.load(uuid, "workflowstatus.json").toFile());
+				existing = WorkflowStatusHelper.loadWorkflowStatus(
+						this.storageService.load(uuid, "workflowstatus.json").toFile(), 
+						this.cloudServiceDefinitions);
 				if (existing != null) {
 					// we could load, so we store it in the local map to avoid loading it again
 					this.workflowStatusMap.put(uuid, existing);
@@ -103,9 +107,11 @@
 				}
 			}
 			model.addAttribute("workflowStatus", existing);
+		} else if (workflowStatus == null || workflowStatus.isDone()) {
+			// only create a new WorkflowStatus in case the current instance is done
+			model.addAttribute("workflowStatus", new WorkflowStatus());
 		}
 		
-		
 		// render the form view
 		return "workflow";
 	}
@@ -131,7 +137,7 @@
 		String uuid = storageService.store(file);
 		
 		workflowStatus.setUuid(uuid);
-		workflowStatus.setName(file.getOriginalFilename() + " - " + formatter.format(new Date()));
+		workflowStatus.setName(uuid + " - " + file.getOriginalFilename() + " - " + formatter.format(new Date()));
 		workflowStatus.addMessage(file.getOriginalFilename() + " successfully uploaded!");
 
 		this.workflowStatusMap.put(uuid, workflowStatus);
@@ -190,42 +196,45 @@
 			@PathVariable(name = "selected") String selected,
 			@ModelAttribute WorkflowStatus ws) {
 
-		ws.addSelectedService(selected);
+		CloudServiceDefinition csd = this.cloudServiceDefinitions.stream()
+				.filter(sd -> sd.getName().equals(selected))
+				.findFirst()
+				.orElse(null);
+		
+		if (csd == null) {
+			return "workflow";
+		}
+
+		ws.addSelectedService(csd);
 		
 		if ("Validation".equals(selected)) {
 			// TODO general: check if the selected service has a configuration URL
 			// TODO build configuration based on provided service configuration
-			CloudServiceDefinition csd = this.cloudServiceDefinitions.stream()
-					.filter(sd -> sd.getName().equals("Validation"))
-					.findFirst()
-					.orElse(null);
 			
-			if (csd != null) {
-				List<String> allProfiles = new ArrayList<>();
-				try {
-					String baseUrl = csd.getBaseUrl();
-					if (!baseUrl.endsWith("/")) {
-						baseUrl += "/";
-					}
-					List<?> jsonResult = Unirest.get(baseUrl + "profiles").asJson().getBody().getArray().toList();
-					allProfiles = jsonResult.stream().map(Object::toString).collect(Collectors.toList());
-				} catch (Exception e) {
-					// do nothing, we will handle configurations in a different way in the future
+			List<String> allProfiles = new ArrayList<>();
+			try {
+				String baseUrl = csd.getBaseUrl();
+				if (!baseUrl.endsWith("/")) {
+					baseUrl += "/";
 				}
+				List<?> jsonResult = Unirest.get(baseUrl + "profiles").asJson().getBody().getArray().toList();
+				allProfiles = jsonResult.stream().map(Object::toString).collect(Collectors.toList());
+			} catch (Exception e) {
+				// do nothing, we will handle configurations in a different way in the future
+			}
+			
+			if (allProfiles != null && !allProfiles.isEmpty()) {
+				ServiceConfiguration config = new ServiceConfiguration(selected);
 				
-				if (allProfiles != null && !allProfiles.isEmpty()) {
-					ServiceConfiguration config = new ServiceConfiguration(selected);
-					
-					ServiceConfigurationParameter validationProfiles = new ServiceConfigurationParameter();
-					validationProfiles.setName("Validation profiles");
-					validationProfiles.setKey("profiles");
-					validationProfiles.setType("String");
-					validationProfiles.setCardinality("multiple");
-					validationProfiles.setPossibleValues(allProfiles);
-					config.addParameter(validationProfiles);
-					
-					ws.addConfiguration(selected, config);
-				}
+				ServiceConfigurationParameter validationProfiles = new ServiceConfigurationParameter();
+				validationProfiles.setName("Validation profiles");
+				validationProfiles.setKey("profiles");
+				validationProfiles.setType("String");
+				validationProfiles.setCardinality("multiple");
+				validationProfiles.setPossibleValues(allProfiles);
+				config.addParameter(validationProfiles);
+				
+				ws.addConfiguration(selected, config);
 			}
 		} else if ("RTC Analysis".equals(selected)) {
 			ServiceConfiguration config = new ServiceConfiguration(selected);
@@ -290,9 +299,15 @@
 			@PathVariable(name = "selected") String selected,
 			@ModelAttribute WorkflowStatus ws) {
 		
-		ws.removeSelectedService(selected);
-		
-		ws.removeConfiguration(selected);
+		CloudServiceDefinition csd = this.cloudServiceDefinitions.stream()
+				.filter(sd -> sd.getName().equals(selected))
+				.findFirst()
+				.orElse(null);
+
+		if (csd != null) {
+			ws.removeSelectedService(csd);
+			ws.removeConfiguration(selected);
+		}
 		
 		// render the form view
 		return "workflow";
@@ -404,7 +419,7 @@
 		public void run() {
 			try {
 				Path inputFile = storageService.load(uuid, originalFilename);
-				for (String service : workflowStatus.getSelectedServices()) {
+				for (CloudServiceDefinition service : workflowStatus.getSelectedServices()) {
 					if (StringUtils.isEmpty(service)) {
 						continue;
 					}
@@ -427,8 +442,10 @@
 				}
 			} finally {
 				workflowStatus.done();
-				WorkflowStatusHelper.saveWorkflowStatus(workflowStatus, storageService.load(this.uuid, "workflowstatus.json").toFile());
-				messagingTemplate.convertAndSend("/topic/process-updates", new ProcessLog(Action.DONE));
+				WorkflowStatusHelper.saveWorkflowStatus(
+						this.workflowStatus, 
+						storageService.load(this.uuid, "workflowstatus.json").toFile());
+				messagingTemplate.convertAndSend("/topic/process-updates", new ProcessLog(Action.DONE).withUuid(this.uuid));
 				// TODO send to session and not broadcast
 //				messagingTemplate.convertAndSendToUser(
 //						this.uuid, 
@@ -438,20 +455,10 @@
 			}
 		}
 		
-		private Path executeCloudService(String serviceName, WorkflowStatus workflowStatus, Path inputFile) {
+		private Path executeCloudService(CloudServiceDefinition csd, WorkflowStatus workflowStatus, Path inputFile) {
 			try {
-				CloudServiceDefinition csd = cloudServiceDefinitions.stream()
-						.filter(sd -> sd.getName().equals(serviceName))
-						.findFirst()
-						.orElse(null);
-				
-				String baseUrl = "";
-				if (csd != null) {
-					baseUrl = csd.getBaseUrl();
-				} else {
-					workflowStatus.addError("No service with name " + serviceName + " found! Workflow stopped!");
-					return null;
-				}
+				String serviceName = csd.getName();
+				String baseUrl = csd.getBaseUrl();
 				
 				// upload to service
 				MultipartBody multipartBody = Unirest.post(baseUrl)
@@ -661,7 +668,7 @@
 				return result;
 
 			} catch (Exception e) {
-				throw new ProcessingFailedException("Error in " + serviceName + " workflow: " + e.getMessage(), e);
+				throw new ProcessingFailedException("Error in " + csd.getName() + " workflow: " + e.getMessage(), e);
 			}
 		}
 		
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowResultController.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowResultController.java
index 3b0418d..f598978 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowResultController.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowResultController.java
@@ -13,8 +13,12 @@
  */
 package org.eclipse.app4mc.cloud.manager;
 
+import java.util.List;
 import java.util.stream.Collectors;
 
+import javax.annotation.Resource;
+
+import org.eclipse.app4mc.cloud.manager.administration.CloudServiceDefinition;
 import org.eclipse.app4mc.cloud.manager.storage.StorageService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
@@ -28,6 +32,9 @@
 	@Autowired
 	private StorageService storageService;
 
+	@Resource(name = "cloudServiceDefinitions")
+	List<CloudServiceDefinition> cloudServiceDefinitions;
+
 	@GetMapping("/workflowResults")
 	public String workflowResults(Model model) {
 		
@@ -35,12 +42,15 @@
 				.map(path -> path.toString().substring(path.toString().lastIndexOf('_') + 1))
 				.collect(Collectors.toMap(
 						uuid -> {
-							WorkflowStatus status = WorkflowStatusHelper.loadWorkflowStatus(storageService.load(uuid, "workflowstatus.json").toFile());
+							WorkflowStatus status = WorkflowStatusHelper.loadWorkflowStatus(
+									this.storageService.load(uuid, "workflowstatus.json").toFile(),
+									this.cloudServiceDefinitions);
 							return (status != null && status.getName() != null) ? status.getName() : uuid;
 						}, 
 						uuid -> MvcUriComponentsBuilder.fromMethodName(WorkflowController.class,
 							"workflow",
 							model,
+							null,
 							uuid).build().toUri().toString()))
 		);
 
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 551c5a7..8ad7488 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
@@ -17,11 +17,13 @@
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 
+import org.eclipse.app4mc.cloud.manager.administration.CloudServiceDefinition;
+
 public class WorkflowStatus {
 
 	private String name;
 	private String uuid;
-	private ArrayList<String> selectedServices = new ArrayList<>();
+	private ArrayList<CloudServiceDefinition> selectedServices = new ArrayList<>();
 	private HashMap<String, ServiceConfiguration> serviceConfigurations = new LinkedHashMap<>();
 	private ArrayList<String> messages = new ArrayList<>();
 	private ArrayList<String> errors = new ArrayList<>();
@@ -46,15 +48,15 @@
 		this.uuid = uuid;
 	}
 	
-	public ArrayList<String> getSelectedServices() {
+	public ArrayList<CloudServiceDefinition> getSelectedServices() {
 		return selectedServices;
 	}
 	
-	public void addSelectedService(String service) {
+	public void addSelectedService(CloudServiceDefinition service) {
 		this.selectedServices.add(service);
 	}
 	
-	public void removeSelectedService(String service) {
+	public void removeSelectedService(CloudServiceDefinition service) {
 		this.selectedServices.remove(service);
 	}
 	
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java
index 3a02bc8..23bf33f 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java
@@ -14,11 +14,22 @@
 package org.eclipse.app4mc.cloud.manager;
 
 import java.io.File;
+import java.io.IOException;
+import java.util.List;
 
+import org.eclipse.app4mc.cloud.manager.administration.CloudServiceDefinition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
 
 public final class WorkflowStatusHelper {
 
@@ -31,6 +42,11 @@
 	public static void saveWorkflowStatus(WorkflowStatus ws, File file) {
 		try {
 			ObjectMapper mapper = new ObjectMapper();
+			
+			SimpleModule module = new SimpleModule();
+			module.addSerializer(CloudServiceDefinition.class, new CloudServiceDefinitionSerializer());
+			mapper.registerModule(module);
+			
 			mapper.writerWithDefaultPrettyPrinter().writeValue(file, ws);
 		} catch (Exception e) {
 			LOG.error("Failed to serialize workflow status", e);
@@ -38,10 +54,15 @@
 		
 	}
 	
-	public static WorkflowStatus loadWorkflowStatus(File file) {
+	public static WorkflowStatus loadWorkflowStatus(File file, List<CloudServiceDefinition> cloudServiceDefinitions) {
 		try {
 			if (file.exists()) {
 				ObjectMapper mapper = new ObjectMapper();
+				
+				SimpleModule module = new SimpleModule();
+				module.addDeserializer(CloudServiceDefinition.class, new CloudServiceDefinitionDeserializer(cloudServiceDefinitions));
+				mapper.registerModule(module);
+				
 				return mapper.readValue(file, WorkflowStatus.class);
 			}
 		} catch (Exception e) {
@@ -49,4 +70,54 @@
 		}
 		return null;
 	}
+	
+	private static class CloudServiceDefinitionSerializer extends StdSerializer<CloudServiceDefinition> {
+
+		private static final long serialVersionUID = 6323664165957974486L;
+
+		public CloudServiceDefinitionSerializer() {
+	        this(null);
+	    }
+	  
+	    public CloudServiceDefinitionSerializer(Class<CloudServiceDefinition> t) {
+	        super(t);
+	    }
+	    
+		@Override
+		public void serialize(CloudServiceDefinition value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+			gen.writeString(value.getName());
+		}
+	}
+	
+	private static class CloudServiceDefinitionDeserializer extends StdDeserializer<CloudServiceDefinition> {
+		
+		private static final long serialVersionUID = -272147724534718705L;
+		
+		private List<CloudServiceDefinition> cloudServiceDefinitions;
+		
+	    public CloudServiceDefinitionDeserializer(List<CloudServiceDefinition> cloudServiceDefinitions) { 
+	        this(null, cloudServiceDefinitions); 
+	    } 
+	 
+	    public CloudServiceDefinitionDeserializer(Class<?> vc, List<CloudServiceDefinition> cloudServiceDefinitions) { 
+	        super(vc);
+	        this.cloudServiceDefinitions = cloudServiceDefinitions;
+	    }
+	 
+	    @Override
+	    public CloudServiceDefinition deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+	        String name = jp.getText();
+
+	        CloudServiceDefinition csd = this.cloudServiceDefinitions.stream()
+					.filter(sd -> sd.getName().equals(name))
+					.findFirst()
+					.orElse(null);
+	        
+	        if (csd != null) {
+	        	return csd;
+	        } else {
+	        	return new CloudServiceDefinition(name, null, null);
+	        }
+	    }
+	}
 }
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java
index ef8f86b..7e20b88 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java
@@ -19,9 +19,12 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
+import org.eclipse.app4mc.cloud.manager.WorkflowStatus;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.util.ResourceUtils;
@@ -32,6 +35,12 @@
 
 	@Bean
     @ApplicationScope
+    public Map<String, WorkflowStatus> workflowStatusMap() {
+		return new HashMap<>();
+	}
+
+	@Bean
+    @ApplicationScope
     public List<CloudServiceDefinition> cloudServiceDefinitions() {
 		ArrayList<CloudServiceDefinition> definitions = new ArrayList<>();
 
diff --git a/manager/src/main/resources/services.txt b/manager/src/main/resources/services.txt
index 076f805..94f4582 100644
--- a/manager/src/main/resources/services.txt
+++ b/manager/src/main/resources/services.txt
@@ -1,13 +1,13 @@
-Migration;http://localhost:8080/app4mc/converter/;Model Migration Service
-Validation;http://localhost:8181/app4mc/validation/;Model Validation Service
-RTC Analysis;http://localhost:8081/app4mc/analysis/;RTC Analysis Service
-RTC Interpreter;http://localhost:8082/app4mc/interpreter/rtc/;RTC Interpreter Service
-Label per Core Interpreter;http://localhost:8084/app4mc/interpreter/label-per-core/;Label per Core Interpreter Service
-Label per Memory Interpreter;http://localhost:8084/app4mc/interpreter/label-per-memory/;Label per Memory Interpreter Service
-Label per Task Interpreter;http://localhost:8084/app4mc/interpreter/label-per-task/;Label per Task Interpreter Service
-Label Size Interpreter;http://localhost:8084/app4mc/interpreter/label-size/;Label Size Interpreter Service
-Chart Visualizer;http://localhost:8083/app4mc/visualization/barchart/;Chart Visualizer Service
-Amalthea 2 INCHRON;https://am2inc.dev1.inchron.de/projects;Amalthea to INCHRON Transformation Service
-Amalthea 2 SystemC;http://localhost:8282/app4mc/amlt2systemc/;Amalthea to SystemC Transformation Service
-APP4MC.Sim;http://139.30.201.29:2323/app4mc/simulation/;APP4MC SystemC Simulation Service
-INCHRON BTF Trace Visualization;https://trace.dev1.inchron.de/traces/;INCHRON BTF Trace Visualization Service
\ No newline at end of file
+Migration;http://localhost:8080/app4mc/converter/;Migrates an input model file to model version 0.9.9
+Validation;http://localhost:8181/app4mc/validation/;Validates the input model file
+RTC Analysis;http://localhost:8081/app4mc/analysis/;Executes Real-Time Calculus (RTC) techniques on a provided Amalthea model to analyse timing properties of heterogeneous embedded systems
+RTC Interpreter;http://localhost:8082/app4mc/interpreter/rtc/;Converts the results of a RTC Analysis into an input for the Chart Visualizer
+Label per Core Interpreter;http://localhost:8084/app4mc/interpreter/label-per-core/;Parses an Amalthea model to inspect label access operations per core
+Label per Memory Interpreter;http://localhost:8084/app4mc/interpreter/label-per-memory/;Parses an Amalthea model to inspect label access operations per memory
+Label per Task Interpreter;http://localhost:8084/app4mc/interpreter/label-per-task/;Parses an Amalthea model to inspect label access operations per task
+Label Size Interpreter;http://localhost:8084/app4mc/interpreter/label-size/;Parses an Amalthea model to inspect the number of labels grouped by size
+Chart Visualizer;http://localhost:8083/app4mc/visualization/barchart/;Visualization of interpreter results
+Amalthea 2 INCHRON;https://am2inc.dev1.inchron.de/projects;Transforms an Amalthea model to an INCHRON model
+Amalthea 2 SystemC;http://localhost:8282/app4mc/amlt2systemc/;Transforms an Amalthea model to simulation code
+APP4MC.Sim;http://139.30.201.29:2323/app4mc/simulation/;Executes simulation and generates a BTF traces
+INCHRON BTF Trace Visualization;https://trace.dev1.inchron.de/traces/;Visualization of trace data provided by INCHRON
\ No newline at end of file
diff --git a/manager/src/main/resources/services_online.txt b/manager/src/main/resources/services_online.txt
index a7649d3..fe5f257 100644
--- a/manager/src/main/resources/services_online.txt
+++ b/manager/src/main/resources/services_online.txt
@@ -1,13 +1,13 @@
-Migration;https://app4mc.eclipseprojects.io/app4mc/converter/;Model Migration Service
-Validation;https://app4mc.eclipseprojects.io/app4mc/validation/;Model Validation Service
-RTC Analysis;https://app4mc.eclipseprojects.io/app4mc/analysis/;RTC Analysis Service
-RTC Interpreter;https://app4mc.eclipseprojects.io/app4mc/interpreter/rtc/;RTC Interpreter Service
-Label per Core Interpreter;https://app4mc.eclipseprojects.io/app4mc/interpreter/label-per-core/;Label per Core Interpreter Service
-Label per Memory Interpreter;https://app4mc.eclipseprojects.io/app4mc/interpreter/label-per-memory/;Label per Memory Interpreter Service
-Label per Task Interpreter;https://app4mc.eclipseprojects.io/app4mc/interpreter/label-per-task/;Label per Task Interpreter Service
-Label Size Interpreter;https://app4mc.eclipseprojects.io/app4mc/interpreter/label-size/;Label Size Interpreter Service
-Chart Visualizer;https://app4mc.eclipseprojects.io/app4mc/visualization/barchart/;Chart Visualizer Service
-Amalthea 2 INCHRON;https://am2inc.dev1.inchron.de/projects;Amalthea to INCHRON Transformation Service
-Amalthea 2 SystemC;https://app4mc.eclipseprojects.io/app4mc/amlt2systemc/;Amalthea to SystemC Transformation Service
-APP4MC.Sim;http://139.30.201.29:2323/app4mc/simulation/;APP4MC SystemC Simulation Service
-INCHRON BTF Trace Visualization;https://trace.dev1.inchron.de/traces/;INCHRON BTF Trace Visualization Service
\ No newline at end of file
+Migration;https://app4mc.eclipseprojects.io/app4mc/converter/;Migrates an input model file to model version 0.9.9
+Validation;https://app4mc.eclipseprojects.io/app4mc/validation/;Validates the input model file
+RTC Analysis;https://app4mc.eclipseprojects.io/app4mc/analysis/;Executes Real-Time Calculus (RTC) techniques on a provided Amalthea model to analyse timing properties of heterogeneous embedded systems
+RTC Interpreter;https://app4mc.eclipseprojects.io/app4mc/interpreter/rtc/;Converts the results of a RTC Analysis into an input for the Chart Visualizer
+Label per Core Interpreter;https://app4mc.eclipseprojects.io/app4mc/interpreter/label-per-core/;Parses an Amalthea model to inspect label access operations per core
+Label per Memory Interpreter;https://app4mc.eclipseprojects.io/app4mc/interpreter/label-per-memory/;Parses an Amalthea model to inspect label access operations per memory
+Label per Task Interpreter;https://app4mc.eclipseprojects.io/app4mc/interpreter/label-per-task/;Parses an Amalthea model to inspect label access operations per task
+Label Size Interpreter;https://app4mc.eclipseprojects.io/app4mc/interpreter/label-size/;Parses an Amalthea model to inspect the number of labels grouped by size
+Chart Visualizer;https://app4mc.eclipseprojects.io/app4mc/visualization/barchart/;Visualization of interpreter results
+Amalthea 2 INCHRON;https://am2inc.dev1.inchron.de/projects;Transforms an Amalthea model to an INCHRON model
+Amalthea 2 SystemC;https://app4mc.eclipseprojects.io/app4mc/amlt2systemc/;Transforms an Amalthea model to simulation code
+APP4MC.Sim;http://139.30.201.29:2323/app4mc/simulation/;Executes simulation and generates a BTF traces
+INCHRON BTF Trace Visualization;https://trace.dev1.inchron.de/traces/;Visualization of trace data provided by INCHRON
\ 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 585fa27..e1730a2 100644
--- a/manager/src/main/resources/static/js/workflow.js
+++ b/manager/src/main/resources/static/js/workflow.js
@@ -24,6 +24,7 @@
 		url: '/select/' + service,
 		success: function(result) {
 			$('#selectedServicesBlock').load('/selectedServices');
+			$('[data-toggle="tooltip"]').tooltip();
 		}
 	});
 }
@@ -59,6 +60,9 @@
 	// load the fragments to be able to update via ajax in later steps
 	loadFragments();
 	
+	// enable all tooltips in the document
+	$('[data-toggle="tooltip"]').tooltip();
+	
 	// initialize and bind the form for ajax submission
 	var options = {
 		beforeSubmit: function() {
@@ -125,12 +129,13 @@
 			//'/user/queue/process-updates',
 	  		function (processLog) {
 		     	var action = JSON.parse(processLog.body).action;
+		     	var uuid = JSON.parse(processLog.body).uuid;
 		     	if (action == 'UPDATE') {
 		        	$('#messagesBlock').load('/messages');
 					$('#errorsBlock').load('/errors');
 		     	} else if (action == 'DONE') {
 		     		disconnect();
-		     		window.location.href = window.location.href.split("?")[0];
+		     		window.location.href = window.location.href.split("?")[0] + '?uuid=' + uuid;
 		 	    }
 			}
 		);
diff --git a/manager/src/main/resources/templates/selectedServices.html b/manager/src/main/resources/templates/selectedServices.html
index d87b196..a7794c1 100644
--- a/manager/src/main/resources/templates/selectedServices.html
+++ b/manager/src/main/resources/templates/selectedServices.html
@@ -8,10 +8,15 @@
     <div th:fragment="servicesList" id="selectedServices" th:object="${workflowStatus}">
 		<ul class="list-group"> 
 			<li th:each="selected : ${workflowStatus.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}" th:classappend="*{uuid != null} ? disabled_service : ''">Service</p> 
+				<p 
+					class="p-0 flex-grow-1 plist" 
+					th:text="${selected.name}" 
+					th:title="${selected.description}"
+					data-toggle="tooltip"
+					th:classappend="*{uuid != null} ? disabled_service : ''">Service</p> 
 				<a 
 					th:if="*{uuid == null}"
-					th:attr="onclick=|removeSelectedServices('${selected}')|" 
+					th:attr="onclick=|removeSelectedServices('${selected.name}')|" 
 					class="btn btn-primary btn-sm btn-rmv">
 					<i class="fas fa-times"></i>
 				</a>
diff --git a/manager/src/main/resources/templates/status.html b/manager/src/main/resources/templates/status.html
index 07ac487..6bd5880 100644
--- a/manager/src/main/resources/templates/status.html
+++ b/manager/src/main/resources/templates/status.html
@@ -28,17 +28,33 @@
 					<p class="p-0 m-0 flex-grow-1" th:text="${result.key}">Result</p> 
 					<a class="btn btn-primary btn-sm mr-1"
 						th:if="${not #strings.endsWith(result.value, 'zip')}" 
-						th:href="@{/{uuid}/files/{resultValue}(uuid=${workflowStatus.uuid},resultValue=${result.value})}"><i class="fas fa-eye"></i></a>
+						th:href="@{/{uuid}/files/{resultValue}(uuid=${workflowStatus.uuid},resultValue=${result.value})}"
+				    	title="View the result in the browser"
+						data-toggle="tooltip"><i class="fas fa-eye"></i></a>
 					<a class="btn btn-secondary btn-sm" 
-						th:href="@{/{uuid}/files/{resultValue}?download=true(uuid=${workflowStatus.uuid},resultValue=${result.value})}"><i class="fas fa-download"></i></a>
+						th:href="@{/{uuid}/files/{resultValue}?download=true(uuid=${workflowStatus.uuid},resultValue=${result.value})}"
+				    	title="Download the processing result"
+						data-toggle="tooltip"><i class="fas fa-download"></i></a>
 				</li>
 			</ul>
 			<div class="row mt-4 mb-4">
 				<div class="col">
-					<a th:href="@{/{uuid}/delete(uuid=${workflowStatus.uuid})}" class="btn btn-danger">Delete</a>
-					<a th:href="@{workflow?uuid={uuid}(uuid=${workflowStatus.uuid})}" class="btn btn-info" onclick="copy(this);return false;">Permanent Link</a>
+					<a th:href="@{/{uuid}/delete(uuid=${workflowStatus.uuid})}"
+						class="btn btn-danger"
+				    	title="Delete the processing result"
+						data-toggle="tooltip">Delete</a>
+					<a th:href="@{workflow?uuid={uuid}(uuid=${workflowStatus.uuid})}" 
+						class="btn btn-info" 
+				    	title="Copy the permanent link of this execution to the clipboard"
+						data-toggle="tooltip"
+						onclick="copy(this);return false;">Permanent Link</a>
 					<div>
-					    <div class="toast" role="alert" aria-live="assertive" aria-atomic="true" style="position:relative;bottom:60px;left:200px;">
+					    <div
+					    	class="toast" 
+					    	role="alert" 
+					    	aria-live="assertive" 
+					    	aria-atomic="true" 
+					    	style="position:relative;bottom:60px;left:200px;">
 							<div class="toast-body">
 								Permanent link copied to clipboard
 							</div>
diff --git a/manager/src/main/resources/templates/workflowResults.html b/manager/src/main/resources/templates/workflowResults.html
index 46cce37..b4addea 100644
--- a/manager/src/main/resources/templates/workflowResults.html
+++ b/manager/src/main/resources/templates/workflowResults.html
@@ -19,7 +19,7 @@
 		</div>
 		<div class="row" th:each="workflow : ${workflows}">
 			<div class="col text-center">
-				<a th:href="${workflow.value}" th:text="${workflow.key}"></a>
+				<a th:href="${workflow.value}" th:text="${#strings.substringAfter(workflow.key, ' - ')}"></a>
 			</div>
 		</div>
 		<div sec:authorize="isAuthenticated()" class="row mt-5">