Added custom error page and basic security for administration

Change-Id: I4b8baad73bef2f7f9ef9ed5d186580e2a9f21c9c
Signed-off-by: Dirk Fauth <Dirk.Fauth@de.bosch.com>
diff --git a/manager/pom.xml b/manager/pom.xml
index 52b57cf..64f0ad7 100644
--- a/manager/pom.xml
+++ b/manager/pom.xml
@@ -58,6 +58,16 @@
 			<artifactId>spring-boot-configuration-processor</artifactId>
 			<optional>true</optional>
 		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-security</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-test</artifactId>
+			<scope>test</scope>
+		</dependency>
 		
 		<dependency>
 		    <groupId>com.konghq</groupId>
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/MvcConfig.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/MvcConfig.java
new file mode 100644
index 0000000..61fbac2
--- /dev/null
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/MvcConfig.java
@@ -0,0 +1,27 @@
+/*********************************************************************************
+ * Copyright (c) 2020 Robert Bosch GmbH and others.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Robert Bosch GmbH - initial API and implementation
+ ********************************************************************************
+ */
+package org.eclipse.app4mc.cloud.manager;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class MvcConfig implements WebMvcConfigurer {
+
+	public void addViewControllers(ViewControllerRegistry registry) {
+		registry.addViewController("/login").setViewName("login");
+	}
+
+}
\ No newline at end of file
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WebSecurityConfig.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WebSecurityConfig.java
new file mode 100644
index 0000000..c81ee1a
--- /dev/null
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WebSecurityConfig.java
@@ -0,0 +1,57 @@
+/*********************************************************************************
+ * Copyright (c) 2020 Robert Bosch GmbH and others.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Robert Bosch GmbH - initial API and implementation
+ ********************************************************************************
+ */
+package org.eclipse.app4mc.cloud.manager;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+
+@Configuration
+@EnableWebSecurity
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+	
+	@Override
+	protected void configure(HttpSecurity http) throws Exception {
+		http
+		.authorizeRequests()
+			.antMatchers("/admin").authenticated()
+			.anyRequest().permitAll()
+			.and()
+		.formLogin()
+			.loginPage("/login")
+			.permitAll()
+			.and()
+		.logout()
+			.permitAll();
+	}
+
+	@Bean
+	@Override
+	public UserDetailsService userDetailsService() {
+		UserDetails user =
+			 User.withDefaultPasswordEncoder()
+				.username("app4mc")
+				.password("Panorama")
+				.roles("ADMIN")
+				.build();
+
+		return new InMemoryUserDetailsManager(user);
+	}
+}
\ No newline at end of file
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 784226d..b5fc464 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
@@ -361,9 +361,9 @@
 					
 					end = System.currentTimeMillis();
 					
-					// don't request for more than 30 seconds
-					if (end - start > 30_000) {
-						workflowStatus.addMessage(serviceName + " status requested for 30 seconds");
+					// don't request for more than 60 seconds
+					if (end - start > 60_000) {
+						workflowStatus.addMessage(serviceName + " status requested for 60 seconds");
 						break;
 					}
 				}
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/AdminController.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/AdminController.java
index 4d1ea27..9b5bc54 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/AdminController.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/AdminController.java
@@ -13,6 +13,7 @@
  */
 package org.eclipse.app4mc.cloud.manager.administration;
 
+import java.util.Iterator;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -23,6 +24,7 @@
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.GetMapping;
 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.RequestParam;
 
@@ -81,6 +83,21 @@
 		return "redirect:/admin";
 	}
 	
+	@PostMapping("/admin/remove/{selected}")
+	public String removeService(
+			@PathVariable(name = "selected") String selected,
+			@ModelAttribute CloudServiceDefinitionDTO dto) {
+		
+		for (Iterator<CloudServiceDefinition> it = cloudServiceDefinitions.iterator(); it.hasNext();) {
+			CloudServiceDefinition csd = it.next();
+			if (selected.equals(csd.getName())) {
+				it.remove();
+			}
+		}
+		
+		return "redirect:/admin";
+	}
+
 	@ModelAttribute("cloudServiceDefinitions")
 	public List<CloudServiceDefinition> cloudServiceDefinitions() {
 		return cloudServiceDefinitions;
diff --git a/manager/src/main/resources/application.properties b/manager/src/main/resources/application.properties
index 52ed629..271822a 100644
--- a/manager/src/main/resources/application.properties
+++ b/manager/src/main/resources/application.properties
@@ -1,2 +1,4 @@
 spring.servlet.multipart.max-file-size=20MB
 spring.servlet.multipart.max-request-size=20MB
+server.error.whitelabel.enabled=false
+server.error.path=/error
\ No newline at end of file
diff --git a/manager/src/main/resources/static/index.html b/manager/src/main/resources/static/index.html
index 8f9d694..feeebf7 100644
--- a/manager/src/main/resources/static/index.html
+++ b/manager/src/main/resources/static/index.html
@@ -24,6 +24,9 @@
 			</div>
 		</div>
 		<div class="row mt-3">
+		    <div class="col text-center text-danger"><h2><em>(under development)</em></h2></div>
+		</div>
+		<div class="row mt-3">
 			<div class="col">
 		    	<a class="btn btn-primary float-right" href="/workflow">Start Workflow</a>
 			</div>
diff --git a/manager/src/main/resources/templates/admin.html b/manager/src/main/resources/templates/admin.html
index c2338c3..07d4c75 100644
--- a/manager/src/main/resources/templates/admin.html
+++ b/manager/src/main/resources/templates/admin.html
@@ -1,11 +1,30 @@
-<html xmlns:th="https://www.thymeleaf.org">
+<html xmlns:th="https://www.thymeleaf.org"
+      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
 <head> 
 <title>APP4MC Cloud Manager - Administration</title> 
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta id="_csrf" name="_csrf" th:content="${_csrf.token}"/>
+<meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}"/>
 <link href="webjars/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
 <script src="webjars/jquery/3.5.1/jquery.min.js"></script>
 <script type="module" src="webjars/popper.js/1.16.0/popper.min.js"></script>
 <script src="webjars/bootstrap/4.5.2/js/bootstrap.min.js"></script>
+<script type="text/javascript">
+function removeService(service) {
+	var token = $('#_csrf').attr('content');
+	var header = $('#_csrf_header').attr('content');
+	$.ajax({
+		type: 'POST',
+		url: '/admin/remove/' + service,
+		beforeSend: function(xhr) {
+            xhr.setRequestHeader(header, token);
+        },
+		success: function(result) {
+			location.reload();
+		}
+	});
+}
+</script>
 </head>
 <body>
 	<div class="container">
@@ -31,6 +50,7 @@
 				                    <th>Service Name</th>
 				                    <th>Service Base URL</th>
 				                    <th>Service Description</th>
+				                    <th></th>
 				                </tr>
 				            </thead>
 				            <tbody>
@@ -38,6 +58,11 @@
 				                    <td><input th:field="*{services[__${stat.index}__].name}" class="form-control"/></td>
 				                    <td><input th:field="*{services[__${stat.index}__].baseUrl}" class="form-control"/></td>
 				                    <td><input th:field="*{services[__${stat.index}__].description}" class="form-control"/></td>
+				                    <td>
+				                    	<a th:if="*{services[__${stat.index}__].name != null}"
+				                    		th:attr="onclick=|removeService('*{services[__${stat.index}__].name}')|" 
+				                    		class="btn btn-secondary btn-sm">remove</a>
+				                    </td>
 				                </tr>
 				            </tbody>
 				        </table>
@@ -102,7 +127,14 @@
 	
 		<div class="row">
 			<div class="col">
-				<a th:href="@{/}" class="btn btn-dark">Go back</a>
+				<form th:action="@{/logout}" method="post">
+					<input class="btn btn-secondary mb-1" type="submit" value="Sign Out"/>
+				</form>
+			</div>
+		</div>
+		<div class="row">
+			<div class="col">
+				<a th:href="@{/}" class="btn btn-dark mb-5">Go back</a>
 			</div>
 		</div>
     </div>
diff --git a/manager/src/main/resources/templates/error.html b/manager/src/main/resources/templates/error.html
new file mode 100644
index 0000000..c968adf
--- /dev/null
+++ b/manager/src/main/resources/templates/error.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html xmlns:th="https://www.thymeleaf.org">
+<head> 
+<title>APP4MC Cloud Manager - Error</title> 
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link href="webjars/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
+<script src="webjars/jquery/3.5.1/jquery.min.js"></script>
+<script type="module" src="webjars/popper.js/1.16.0/popper.min.js"></script>
+<script src="webjars/bootstrap/4.5.2/js/bootstrap.min.js"></script>
+</head>
+<body>
+	<div class="container">
+		<div class="row justify-content-center">
+			<div class="col text-center" style="height:200px">
+				<img class="img-fluid" style="height:100%" src="/images/Logo_Panorama.png"/>
+			</div>
+		</div>
+		<div class="row align-items-end">
+			<div class="col">
+			    <div class="content-heading"><h1>An error occured</h1></div>
+			</div>
+		</div>
+		<div class="row mt-3">
+			<div class="col-2">
+				Date
+			</div>
+			<div class="col">
+				<span th:text="${timestamp}"></span>
+			</div>
+		</div>
+		<div class="row mt-3">
+			<div class="col-2">
+				Path
+			</div>
+			<div class="col">
+				<span th:text="${path}"></span>
+			</div>
+		</div>
+		<div class="row mt-3">
+			<div class="col-2">
+				Error
+			</div>
+			<div class="col">
+				<span th:text="${error}"></span>
+			</div>
+		</div>
+		<div class="row mt-3">
+			<div class="col-2">
+				Status
+			</div>
+			<div class="col">
+				<span th:text="${status}"></span>
+			</div>
+		</div>
+		<div class="row mt-3">
+			<div class="col-2">
+				Message
+			</div>
+			<div class="col">
+				<span th:text="${message}"></span>
+			</div>
+		</div>
+		<div class="row mt-3">
+			<div class="col-2">
+				Exception
+			</div>
+			<div class="col">
+				<span th:text="${exception}"></span>
+			</div>
+		</div>
+		<div class="row mt-3">
+			<div class="col-2">
+				Trace
+			</div>
+			<div class="col">
+				<pre th:text="${trace}"></pre>
+			</div>
+		</div>
+		<div class="row">
+			<div class="col">
+				<a th:href="@{/}" class="btn btn-dark mt-5 mb-5">Go back</a>
+			</div>
+		</div>
+    </div>
+</body>
+</html>
diff --git a/manager/src/main/resources/templates/login.html b/manager/src/main/resources/templates/login.html
new file mode 100644
index 0000000..0ac12f1
--- /dev/null
+++ b/manager/src/main/resources/templates/login.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
+      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
+<head>
+<title>APP4MC Cloud Manager - Login</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link href="webjars/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
+<script src="webjars/jquery/3.5.1/jquery.min.js"></script>
+<script type="module" src="webjars/popper.js/1.16.0/popper.min.js"></script>
+<script src="webjars/bootstrap/4.5.2/js/bootstrap.min.js"></script>
+</head>
+<body>
+	<div class="container">
+		<div class="row justify-content-center">
+			<div class="col text-center" style="height:200px">
+				<img class="img-fluid" style="height:100%" src="/images/Logo_Panorama.png"/>
+			</div>
+		</div>
+		<div class="row">
+			<div class="col text-center">
+			    <div class="content-heading"><h1>Login</h1></div>
+			</div>
+		</div>
+		<div class="row mt-3 mb-3">
+			<div th:if="${param.error}">
+			    <div class="content-heading"><h2>Invalid username and password.</h2></div>
+			</div>
+		</div>
+		<div class="row mt-3 mb-3">
+	        <div th:if="${param.logout}">
+			    <div class="content-heading"><h2>You have been logged out.</h2></div>
+	        </div>
+		</div>
+		
+	    <form th:action="@{/login}" method="post">
+		    <fieldset>
+
+				<div class="form-row">
+					<div class="form-group col">
+						<label for="username">User Name</label>
+						<input type="text" id="username" name="username"class="form-control"/>
+					</div>
+				</div>
+				<div class="form-row">
+					<div class="form-group col">
+						<label for="password">Password</label>
+						<input type="password" id="password" name="password" class="form-control"/>
+					</div>
+				</div>
+				<div class="form-row">
+					<div class="col text-center">
+						<input type="submit" value="Sign In" class="btn btn-primary mt-2"/>
+					</div>
+				</div>
+			</fieldset>
+        </form>
+
+		<div class="row">
+			<div class="col">
+				<a th:href="@{/}" class="btn btn-dark">Go back</a>
+			</div>
+		</div>
+    </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 08100a5..de9203f 100644
--- a/manager/src/main/resources/templates/workflow.html
+++ b/manager/src/main/resources/templates/workflow.html
@@ -87,7 +87,7 @@
 			</div>
 			<div class="form-row">
 				<div class="col text-center">
-					<input type="submit" value="Start workflow" class="btn btn-primary mt-2"/>
+					<input type="submit" value="Start workflow" class="btn btn-primary mt-2" onClick="this.form.submit(); this.disabled=true; this.value='Processing…'; "/>
 				</div>
 			</div>
 		</form>
@@ -106,7 +106,7 @@
 	
 		<div class="row">
 			<div class="col">
-				<a th:href="@{/}" class="btn btn-dark">Go back</a>
+				<a th:href="@{/}" class="btn btn-dark mb-5">Go back</a>
 			</div>
 		</div>