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>