Code format hawkbit-runtime (#1947)

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-11-05 11:38:12 +02:00
committed by GitHub
parent 548c8d28a6
commit 3e469fa58c
49 changed files with 621 additions and 566 deletions

View File

@@ -3,10 +3,10 @@ hawkBit Sandbox
## Try out the update server in our hawkBit sandbox
- try out Management API https://hawkbit.eclipseprojects.io/rest/v1/targets (don't forget basic auth header; username: demo, passwd: demo)
- try out Management API https://hawkbit.eclipseprojects.io/rest/v1/targets (don't forget basic auth header; username:
demo, passwd: demo)
- try out DDI API https://hawkbit.eclipseprojects.io/DEFAULT/controller/v1/MYTESTDEVICE
## Sandbox Setup
**1. File Structure**
@@ -35,11 +35,12 @@ Reset the Sandbox once a week with a cron-job and log its output.
$ sudo /.sandbox/scripts/initialize-cronjobs.sh
```
**3. Nginx Reverse Proxy**
Start the stack for the Nginx reverse proxy with Let's Encrypt support. Thanks to JrCs for providing the Docker container
and instructions with his [Docker-Letsencrypt-Nginx-Companion](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion).
Start the stack for the Nginx reverse proxy with Let's Encrypt support. Thanks to JrCs for providing the Docker
container
and instructions with
his [Docker-Letsencrypt-Nginx-Companion](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion).
```
$ docker stack deploy -c /.sandbox/stacks/sandbox/docker-compose-stack.yml proxy

View File

@@ -27,21 +27,21 @@ services:
nginx:
image: "jwilder/nginx-proxy"
ports:
- 80:80
- 443:443
- 80:80
- 443:443
networks:
- proxy
- proxy
volumes:
- certificates:/etc/nginx/certs:ro
- vhost:/etc/nginx/vhost.d
- challange:/usr/share/nginx/html
- /var/run/docker.sock:/tmp/docker.sock:ro
- certificates:/etc/nginx/certs:ro
- vhost:/etc/nginx/vhost.d
- challange:/usr/share/nginx/html
- /var/run/docker.sock:/tmp/docker.sock:ro
deploy:
replicas: 1
restart_policy:
condition: on-failure
labels:
- "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
- "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
# ---------------------
# lets encrypt nginx-proxy companion
@@ -49,12 +49,12 @@ services:
letsencrypt:
image: "jrcs/letsencrypt-nginx-proxy-companion"
networks:
- proxy
- proxy
volumes:
- certificates:/etc/nginx/certs:rw
- /var/run/docker.sock:/var/run/docker.sock:ro
- vhost:/etc/nginx/vhost.d
- challange:/usr/share/nginx/html
- certificates:/etc/nginx/certs:rw
- /var/run/docker.sock:/var/run/docker.sock:ro
- vhost:/etc/nginx/vhost.d
- challange:/usr/share/nginx/html
deploy:
replicas: 1
restart_policy:

View File

@@ -28,18 +28,18 @@ services:
hawkbit:
image: "hawkbit/hawkbit-update-server:latest"
networks:
- proxy_proxy
- sandbox
- proxy_proxy
- sandbox
deploy:
restart_policy:
condition: on-failure
environment:
- 'SPRING_PROFILES_ACTIVE=cloudsandbox'
- 'SPRING_RABBITMQ_HOST=rabbitmq'
- 'SPRING_RABBITMQ_USERNAME=guest'
- 'SPRING_RABBITMQ_PASSWORD=guest'
- 'VIRTUAL_HOST=hawkbit.eclipseprojects.io'
- 'LETSENCRYPT_HOST=hawkbit.eclipseprojects.io'
- 'SPRING_PROFILES_ACTIVE=cloudsandbox'
- 'SPRING_RABBITMQ_HOST=rabbitmq'
- 'SPRING_RABBITMQ_USERNAME=guest'
- 'SPRING_RABBITMQ_PASSWORD=guest'
- 'VIRTUAL_HOST=hawkbit.eclipseprojects.io'
- 'LETSENCRYPT_HOST=hawkbit.eclipseprojects.io'
# ---------------------
# hawkBit simulator
@@ -47,16 +47,16 @@ services:
simulator:
image: "hawkbit/hawkbit-device-simulator:latest"
networks:
- sandbox
- sandbox
deploy:
restart_policy:
condition: on-failure
environment:
- 'SPRING_RABBITMQ_VIRTUALHOST=/'
- 'SPRING_RABBITMQ_HOST=rabbitmq'
- 'SPRING_RABBITMQ_PORT=5672'
- 'SPRING_RABBITMQ_USERNAME=guest'
- 'SPRING_RABBITMQ_PASSWORD=guest'
- 'SPRING_RABBITMQ_VIRTUALHOST=/'
- 'SPRING_RABBITMQ_HOST=rabbitmq'
- 'SPRING_RABBITMQ_PORT=5672'
- 'SPRING_RABBITMQ_USERNAME=guest'
- 'SPRING_RABBITMQ_PASSWORD=guest'
# ---------------------
# RabbitMQ service
@@ -64,7 +64,7 @@ services:
rabbitmq:
image: "rabbitmq:3-management"
networks:
- sandbox
- sandbox
deploy:
restart_policy:
condition: on-failure

View File

@@ -1,15 +1,15 @@
hawkBit Runtime
===
| Folder | Description |
|--------------------------|------------------------------------------------------------------------------------------------------------------------------|
| `.sandbox/` | Content of the hawkBit sandbox installation running on [hawkbit.eclipseprojects.io](https://hawkbit.eclipseprojects.io/UI/). |
| `docker/` | Docker related files, such es Dockerfiles, compose and stack files to quickly start up an hawkBit. |
| `docker/docker_build/` | Docker images build related files, such es Dockerfiles and build shell scripts. |
| `hawkbit-update-server/` | Spring-Boot application of hawkBit. Monolith containing all services. |
| `hawkbit-ddi-server/` | Spring-Boot application of hawkBit DDI server. |
| `hawkbit-dmf-server/` | Spring-Boot application of hawkBit DMF server. |
| `hawkbit-mgmt-server/` | Spring-Boot application of hawkBit Management server. Provides REST Management API and rollouts / auto assigment processing |
| Folder | Description |
|--------------------------|----------------------------------------------------------------------------------------------------------------------------|
| `.sandbox/` | Content of the hawkBit sandbox installation running on [hawkbit.eclipseprojects.io](https://hawkbit.eclipseprojects.io/UI/). |
| `docker/` | Docker related files, such es Dockerfiles, compose and stack files to quickly start up an hawkBit. |
| `docker/docker_build/` | Docker images build related files, such es Dockerfiles and build shell scripts. |
| `hawkbit-update-server/` | Spring-Boot application of hawkBit. Monolith containing all services. |
| `hawkbit-ddi-server/` | Spring-Boot application of hawkBit DDI server. |
| `hawkbit-dmf-server/` | Spring-Boot application of hawkBit DMF server. |
| `hawkbit-mgmt-server/` | Spring-Boot application of hawkBit Management server. Provides REST Management API and rollouts / auto assigment processing |
Note: micro service setup requires all services using DB to use same shared DB. So, they don't work with default in memory H2 database. Docker compose with mysql shows an example setup.
Note: micro service setup requires all services using DB to use same shared DB. So, they don't work with default in
memory H2 database. Docker compose with mysql shows an example setup.

View File

@@ -20,10 +20,10 @@ Start the hawkBit Update Server together with an MySQL and RabbitMQ instance as
$ docker-compose up -d
```
## C: Docker Stack
Start the hawkBit Update Server and Device Simulator together with an MySQL and RabbitMQ instance as services within a swarm
Start the hawkBit Update Server and Device Simulator together with an MySQL and RabbitMQ instance as services within a
swarm
```bash
$ docker swarm init
@@ -32,16 +32,18 @@ $ docker stack deploy -c docker-compose-deps-mysql.yml hawkbit
# Access
| Service / Container | URL | Login | A | B | C |
|---|---|---|---|---|---|
| hawkBit Update Server | [http://localhost:8080/](http://localhost:8080/) | admin:admin | &#10003; | &#10003; | &#10003; |
| hawkBit Device Simulator | [http://localhost:8083/](http://localhost:8083/) | - | | | &#10003; |
| MySQL | localhost:3306/hawkbit | root | | &#10003; | &#10003; |
| RabbitMQ | [http://localhost:15672](http://localhost:15672) | guest:guest | | &#10003; | &#10003; |
| Service / Container | URL | Login | A | B | C |
|--------------------------|--------------------------------------------------|-------------|----------|----------|----------|
| hawkBit Update Server | [http://localhost:8080/](http://localhost:8080/) | admin:admin | &#10003; | &#10003; | &#10003; |
| hawkBit Device Simulator | [http://localhost:8083/](http://localhost:8083/) | - | | | &#10003; |
| MySQL | localhost:3306/hawkbit | root | | &#10003; | &#10003; |
| RabbitMQ | [http://localhost:15672](http://localhost:15672) | guest:guest | | &#10003; | &#10003; |
# Configuration
You can override application.properties by setting an environment variable SPRING_APPLICATION_JSON for hawkbit container.
You can override application.properties by setting an environment variable SPRING_APPLICATION_JSON for hawkbit
container.
```
hawkbit:
image: "hawkbit/hawkbit-update-server:latest-mysql"

View File

@@ -1,55 +1,82 @@
hawkBit Docker Build
===
This directory contains docker files for building both hawkBit docker flavours:
* _standard_ - hawkBit images without mysql driver
* _mysql_ - with MariaDB Java connector with support for MySQL.
Both flavours of are almost the same, just mysql has in addition a MariaDB Java connector and is started, by default, with mysql Spring profile.
Both flavours of are almost the same, just mysql has in addition a MariaDB Java connector and is started, by default,
with mysql Spring profile.
For every flavour there are two build types:
* _release_ - uses officially released hawkBit versions, downloading them from https://repo1.maven.org
* _development/dev_ - uses the local maven repository with built by developer (or just downloaded from any maven repository) hawkBit applications
* _development/dev_ - uses the local maven repository with built by developer (or just downloaded from any maven
repository) hawkBit applications
## Build overview
Building images supports the following build arguments (i.e. ARG-s which could be passed using _--build-arg_):
* _JAVA_VERSION_ - **[OPTIONAL, if not set a default is used]** the Java version of the eclipse-temurin jre-alpine base image to be used.
* _HAWKBIT_APP_ - **[OPTIONAL, if not set _hawkbit-update-server_ is used]** the application to be build. Currently, there is just _hawkbit-update-server_ but in future, if hawkBit is split to micro-services, there could be different micro-service apps.
* _HAWKBIT_VERSION_ - **[OPTIONAL, if not set a default, should be the last officially released version, is used]** the application version
* _JAVA_VERSION_ - **[OPTIONAL, if not set a default is used]** the Java version of the eclipse-temurin jre-alpine base
image to be used.
* _HAWKBIT_APP_ - **[OPTIONAL, if not set _hawkbit-update-server_ is used]** the application to be build. Currently,
there is just _hawkbit-update-server_ but in future, if hawkBit is split to micro-services, there could be different
micro-service apps.
* _HAWKBIT_VERSION_ - **[OPTIONAL, if not set a default, should be the last officially released version, is used]** the
application version
* _CONTAINER_PORT_ - **[OPTIONAL, if not set 8080 is used]** on which the app opens the http server (if available)
* _MARIADB_DRIVER_VERSION_ (mysql flavours only!) - **[OPTIONAL, if not set a default is used]** the version of MariaDB connector to be used
* _MARIADB_DRIVER_VERSION_ (mysql flavours only!) - **[OPTIONAL, if not set a default is used]** the version of MariaDB
connector to be used
Additionally, tge _development_ builds shall be started with docker build context the local maven repository
## Build standard
Standard flavour could be build, for example, with (fixed version 0.4.1 is just an example):
```shell
docker build --build-arg HAWKBIT_APP=hawkbit-update-server --build-arg HAWKBIT_VERSION=0.4.1 -t hawkbit_update_server:0.4.1 . -f Dockerfile
```
or just by:
```shell
docker build --build-arg HAWKBIT_VERSION=0.4.1 -t hawkbit_update_server:0.4.1 .
```
having that docker uses by default _Dockerfile_ and the _hawkbit-update-server_ is the default _HAWKBIT_APP_.
To build standard development docker images, e.g. snapshot based, you could use something like:
```shell
docker build -t hawkbit_update_server:0-SNAPSHOT -f Dockerfile_dev ~/.m2/repository
```
Note that here you have to use your maven repository containing the hawkBit app as docker build context, in the example case _~/.m2/repository_
Note that here you have to use your maven repository containing the hawkBit app as docker build context, in the example
case _~/.m2/repository_
## Build mysql
Mysql flavour could be build, for example, with:
```shell
docker build --build-arg HAWKBIT_APP=hawkbit-update-server --build-arg HAWKBIT_VERSION=0.4.1 -t hawkbit_update_server:0.4.1-mysql . -f Dockerfile-mysql
```
or just by:
```shell
docker build --build-arg -t hawkbit_update_server:0.4.1-mysql --build-arg HAWKBIT_VERSION=0.4.1 . -f Dockerfile-mysql
```
having that the _hawkbit-update-server_ is the default _HAWKBIT_APP_.
To build development mysql docker images, e.g. snapshot based, you could use something like:
```shell
docker build -t hawkbit_update_server:0-SNAPSHOT-mysql -f Dockerfile_dev-mysql ~/.m2/repository
```
Note that here you have to use your maven repository containing the hawkBit app as docker build context, in the example case _~/.m2/repository_
Note that here you have to use your maven repository containing the hawkBit app as docker build context, in the example
case _~/.m2/repository_

View File

@@ -1,5 +1,7 @@
# hawkBit DDI Server (EXPERIMENTAL!)
The hawkBit DDI Server is a standalone spring-boot application with an embedded servlet container. It should be started with at least hawkbit-mgmt-server.
The hawkBit DDI Server is a standalone spring-boot application with an embedded servlet container. It should be started
with at least hawkbit-mgmt-server.
## On your own workstation
@@ -18,14 +20,22 @@ run org.eclipse.hawkbit.app.ddi.DDIStart
```
### Usage
The Management API can be accessed via http://localhost:8081/rest/v1
The root url http://localhost:8081 will redirect directly to the Swagger Management UI
### Clustering (Experimental!!!)
The micro-service instances are configured to communicate via Spring Cloud Bus. You could run multiple instances of any micro-service but hawkbit-mgmt-server. Management server run some schedulers which shall not run simultaneously - e.g. auto assignment checker and rollouts executor. To run multiple management server instances you shall do some extensions of hawkbit to ensure that they wont run schedulers simultaneously or you shall configure all instances but one to do not run schedulers!
The micro-service instances are configured to communicate via Spring Cloud Bus. You could run multiple instances of any
micro-service but hawkbit-mgmt-server. Management server run some schedulers which shall not run simultaneously - e.g.
auto assignment checker and rollouts executor. To run multiple management server instances you shall do some extensions
of hawkbit to ensure that they wont run schedulers simultaneously or you shall configure all instances but one to do not
run schedulers!
## Optional Protostuff for Sprign cloud bus
The micro-service instances are configured to communicate via Spring Cloud Bus. Optionally, you could use [Protostuff](https://github.com/protostuff/protostuff) based message payload serialization for improved performance.
The micro-service instances are configured to communicate via Spring Cloud Bus. Optionally, you could
use [Protostuff](https://github.com/protostuff/protostuff) based message payload serialization for improved performance.
**Note**: If Protostuff is enabled it shall be enabled on all microservices!

View File

@@ -9,7 +9,7 @@
SPDX-License-Identifier: EPL-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>

View File

@@ -19,7 +19,6 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView;
/**
* A {@link SpringBootApplication} annotated class with a main method to start.
* The minimal configuration for the stand alone hawkBit DDI server.
@@ -31,8 +30,7 @@ public class DDIStart {
/**
* Main method to start the spring-boot application.
*
* @param args
* the VM arguments.
* @param args the VM arguments.
*/
public static void main(final String[] args) {
SpringApplication.run(DDIStart.class, args);

View File

@@ -24,11 +24,10 @@ import org.springframework.web.context.WebApplicationContext;
@ExtendWith(SharedSqlTestDatabaseExtension.class)
public abstract class AbstractSecurityTest {
protected MockMvc mvc;
@Autowired
private WebApplicationContext context;
protected MockMvc mvc;
@BeforeEach
public void setup() {
final DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context)

View File

@@ -9,17 +9,14 @@
*/
package org.eclipse.hawkbit.app.ddi;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import io.qameta.allure.Description;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.security.web.firewall.RequestRejectedException;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = { "hawkbit.server.security.allowedHostNames=localhost",

View File

@@ -9,6 +9,9 @@
*/
package org.eclipse.hawkbit.app.ddi;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
@@ -18,25 +21,22 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.TestPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@Feature("Integration Test - Security")
@Story("PreAuthorized enabled")
@TestPropertySource(properties = {"spring.flyway.enabled=true"})
@TestPropertySource(properties = { "spring.flyway.enabled=true" })
public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test
@Description("Tests whether request fail if a role is forbidden for the user")
@WithUser(authorities = { SpPermission.READ_TARGET } )
@WithUser(authorities = { SpPermission.READ_TARGET })
public void failIfNoRole() throws Exception {
mvc.perform(get("/DEFAULT/controller/v1/controllerId")).andExpect(result ->
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()));
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()));
}
@Test
@Description("Tests whether request succeed if a role is granted for the user")
@WithUser(authorities = { SpPermission.SpringEvalExpressions.CONTROLLER_ROLE })
@WithUser(authorities = { SpPermission.SpringEvalExpressions.CONTROLLER_ROLE })
public void successIfHasRole() throws Exception {
mvc.perform(get("/DEFAULT/controller/v1/controllerId")).andExpect(result -> {
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());

View File

@@ -1,5 +1,7 @@
# hawkBit DMF Server (EXPERIMENTAL!)
The hawkBit DMF Server is a standalone spring-boot application with an embedded servlet container. It should be started with at least hawkbit-mgmt-server.
The hawkBit DMF Server is a standalone spring-boot application with an embedded servlet container. It should be started
with at least hawkbit-mgmt-server.
## On your own workstation
@@ -18,10 +20,17 @@ run org.eclipse.hawkbit.app.dmf.DMFStart
```
### Clustering (Experimental!!!)
The micro-service instances are configured to communicate via Spring Cloud Bus. You could run multiple instances of any micro-service but hawkbit-mgmt-server. Management server run some schedulers which shall not run simultaneously - e.g. auto assignment checker and rollouts executor. To run multiple management server instances you shall do some extensions of hawkbit to ensure that they wont run schedulers simultaneously or you shall configure all instances but one to do not run schedulers!
The micro-service instances are configured to communicate via Spring Cloud Bus. You could run multiple instances of any
micro-service but hawkbit-mgmt-server. Management server run some schedulers which shall not run simultaneously - e.g.
auto assignment checker and rollouts executor. To run multiple management server instances you shall do some extensions
of hawkbit to ensure that they wont run schedulers simultaneously or you shall configure all instances but one to do not
run schedulers!
## Optional Protostuff for Sprign cloud bus
The micro-service instances are configured to communicate via Spring Cloud Bus. Optionally, you could use [Protostuff](https://github.com/protostuff/protostuff) based message payload serialization for improved performance.
The micro-service instances are configured to communicate via Spring Cloud Bus. Optionally, you could
use [Protostuff](https://github.com/protostuff/protostuff) based message payload serialization for improved performance.
**Note**: If Protostuff is enabled it shall be enabled on all microservices!

View File

@@ -9,7 +9,7 @@
SPDX-License-Identifier: EPL-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>

View File

@@ -1,5 +1,7 @@
# hawkBit Management Server (EXPERIMENTAL!)
The hawkBit Management Server is a standalone spring-boot application with an embedded servlet container. It should be started with at least one (or both) of the device interface servers - hawkbit-ddi-server or/and hawkbit-dmf-server.
The hawkBit Management Server is a standalone spring-boot application with an embedded servlet container. It should be
started with at least one (or both) of the device interface servers - hawkbit-ddi-server or/and hawkbit-dmf-server.
## On your own workstation
@@ -18,14 +20,22 @@ run org.eclipse.hawkbit.app.mgmt.MgmtServerStart
```
### Usage
The Management API can be accessed via http://localhost:8080/rest/v1
The root url http://localhost:8080 will redirect directly to the Swagger Management UI
### Clustering (Experimental!!!)
The micro-service instances are configured to communicate via Spring Cloud Bus. You could run multiple instances of any micro-service but hawkbit-mgmt-server. Management server run some schedulers which shall not run simultaneously - e.g. auto assignment checker and rollouts executor. To run multiple management server instances you shall do some extensions of hawkbit to ensure that they wont run schedulers simultaneously or you shall configure all instances but one to do not run schedulers!
The micro-service instances are configured to communicate via Spring Cloud Bus. You could run multiple instances of any
micro-service but hawkbit-mgmt-server. Management server run some schedulers which shall not run simultaneously - e.g.
auto assignment checker and rollouts executor. To run multiple management server instances you shall do some extensions
of hawkbit to ensure that they wont run schedulers simultaneously or you shall configure all instances but one to do not
run schedulers!
## Optional Protostuff for Sprign cloud bus
The micro-service instances are configured to communicate via Spring Cloud Bus. Optionally, you could use [Protostuff](https://github.com/protostuff/protostuff) based message payload serialization for improved performance.
The micro-service instances are configured to communicate via Spring Cloud Bus. Optionally, you could
use [Protostuff](https://github.com/protostuff/protostuff) based message payload serialization for improved performance.
**Note**: If Protostuff is enabled it shall be enabled on all microservices!

View File

@@ -9,7 +9,7 @@
SPDX-License-Identifier: EPL-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>

View File

@@ -37,10 +37,8 @@ public class ErrorController extends BasicErrorController {
/**
* A new {@link ErrorController}.
*
* @param errorAttributes
* the error attributes
* @param serverProperties
* configuration properties
* @param errorAttributes the error attributes
* @param serverProperties configuration properties
*/
public ErrorController(final ErrorAttributes errorAttributes, final ServerProperties serverProperties) {
super(errorAttributes, serverProperties.getError());

View File

@@ -22,7 +22,6 @@ import org.springframework.web.servlet.view.RedirectView;
/**
* A {@link SpringBootApplication} annotated class with a main method to start.
* The minimal configuration for the stand alone hawkBit server.
*
*/
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
@EnableHawkbitManagedSecurityConfiguration

View File

@@ -24,11 +24,10 @@ import org.springframework.web.context.WebApplicationContext;
@ExtendWith(SharedSqlTestDatabaseExtension.class)
public abstract class AbstractSecurityTest {
protected MockMvc mvc;
@Autowired
private WebApplicationContext context;
protected MockMvc mvc;
@BeforeEach
public void setup() {
final DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context)

View File

@@ -9,17 +9,14 @@
*/
package org.eclipse.hawkbit.app.mgmt;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import io.qameta.allure.Description;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.security.web.firewall.RequestRejectedException;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = { "hawkbit.server.security.allowedHostNames=localhost",

View File

@@ -14,19 +14,17 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.eclipse.hawkbit.im.authentication.SpRole;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.test.web.servlet.ResultActions;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
@SpringBootTest(
properties = {
"hawkbit.dmf.rabbitmq.enabled=false",
@@ -35,7 +33,7 @@ import io.qameta.allure.Story;
CorsTest.ALLOWED_ORIGIN_FIRST + "," +
CorsTest.ALLOWED_ORIGIN_SECOND,
"hawkbit.server.security.cors.exposedHeaders=" +
HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN})
HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN })
@Feature("Integration Test - Security")
@Story("CORS")
public class CorsTest extends AbstractSecurityTest {
@@ -63,8 +61,8 @@ public class CorsTest extends AbstractSecurityTest {
final String invalidCorsUrlResponseBody = performOptionsRequestToUrlWithOrigin(
MgmtRestConstants.BASE_SYSTEM_MAPPING, ALLOWED_ORIGIN_FIRST).andExpect(status().isForbidden())
.andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).andReturn()
.getResponse().getContentAsString();
.andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).andReturn()
.getResponse().getContentAsString();
assertThat(invalidCorsUrlResponseBody).isEqualTo(INVALID_CORS_REQUEST);
}

View File

@@ -9,6 +9,11 @@
*/
package org.eclipse.hawkbit.app.mgmt;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import java.util.HashMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
@@ -19,26 +24,21 @@ import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import java.util.HashMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@Feature("Integration Test - Security")
@Story("PreAuthorized enabled")
public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test
@Description("Tests whether request fail if a role is forbidden for the user")
@WithUser(authorities = { SpPermission.READ_TARGET } )
@WithUser(authorities = { SpPermission.READ_TARGET })
public void failIfNoRole() throws Exception {
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result ->
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()));
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()));
}
@Test
@Description("Tests whether request succeed if a role is granted for the user")
@WithUser(authorities = { SpPermission.READ_REPOSITORY })
@WithUser(authorities = { SpPermission.READ_REPOSITORY })
public void successIfHasRole() throws Exception {
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> {
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
@@ -47,7 +47,7 @@ public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test
@Description("Tests whether request succeed if a role is granted for the user")
@WithUser(authorities = { SpRole.TENANT_ADMIN })
@WithUser(authorities = { SpRole.TENANT_ADMIN })
public void successIfHasTenantAdminRole() throws Exception {
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> {
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
@@ -57,7 +57,7 @@ public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test
@Description("Tests whether read tenant config request fail if a tenant config (or read read) is not " +
"granted for the user")
@WithUser(authorities = { SpPermission.READ_TARGET } )
@WithUser(authorities = { SpPermission.READ_TARGET })
public void onlyDSIfNoTenantConfig() throws Exception {
mvc.perform(get("/rest/v1/system/configs")).andExpect(result -> {
// returns default DS type because of READ_TARGET
@@ -72,9 +72,9 @@ public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test
@Description("Tests whether read tenant config request succeed if a tenant config (not read explicitly) is " +
"granted for the user")
@WithUser(authorities = { SpPermission.TENANT_CONFIGURATION })
@WithUser(authorities = { SpPermission.TENANT_CONFIGURATION })
public void successIfHasTenantConfig() throws Exception {
mvc.perform(get("/rest/v1/system/configs")).andExpect(result ->
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));
}
}

View File

@@ -9,8 +9,9 @@
SPDX-License-Identifier: EPL-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.hawkbit</groupId>

View File

@@ -9,8 +9,8 @@
*/
package org.eclipse.hawkbit.ui.simple;
import org.eclipse.hawkbit.sdk.HawkbitClient;
import org.eclipse.hawkbit.sdk.Tenant;
import java.util.function.Supplier;
import feign.FeignException;
import lombok.Getter;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi;
@@ -24,8 +24,9 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetTagRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetTypeRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTenantManagementRestApi;
import org.eclipse.hawkbit.sdk.HawkbitClient;
import org.eclipse.hawkbit.sdk.Tenant;
import org.springframework.http.ResponseEntity;
import java.util.function.Supplier;
@Getter
public class HawkbitMgmtClient {
@@ -49,7 +50,7 @@ public class HawkbitMgmtClient {
this.tenant = tenant;
this.hawkbitClient = hawkbitClient;
softwareModuleRestApi = service(MgmtSoftwareModuleRestApi .class);
softwareModuleRestApi = service(MgmtSoftwareModuleRestApi.class);
softwareModuleTypeRestApi = service(MgmtSoftwareModuleTypeRestApi.class);
distributionSetRestApi = service(MgmtDistributionSetRestApi.class);
distributionSetTypeRestApi = service(MgmtDistributionSetTypeRestApi.class);
@@ -77,6 +78,7 @@ public class HawkbitMgmtClient {
boolean hasTargetRead() {
return hasRead(() -> targetRestApi.getTarget("_#ETE$ER"));
}
boolean hasConfigRead() {
return hasRead(() -> tenantManagementRestApi.getTenantConfigurationValue("_#ETE$ER"));
}

View File

@@ -9,13 +9,8 @@
*/
package org.eclipse.hawkbit.ui.simple;
import org.eclipse.hawkbit.ui.simple.view.TargetView;
import org.eclipse.hawkbit.ui.simple.view.RolloutView;
import org.eclipse.hawkbit.ui.simple.security.AuthenticatedUser;
import org.eclipse.hawkbit.ui.simple.view.AboutView;
import org.eclipse.hawkbit.ui.simple.view.ConfigView;
import org.eclipse.hawkbit.ui.simple.view.DistributionSetView;
import org.eclipse.hawkbit.ui.simple.view.SoftwareModuleView;
import java.util.Optional;
import com.vaadin.flow.component.Unit;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
@@ -39,18 +34,22 @@ import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.server.auth.AccessAnnotationChecker;
import com.vaadin.flow.theme.lumo.LumoUtility;
import java.util.Optional;
import org.eclipse.hawkbit.ui.simple.security.AuthenticatedUser;
import org.eclipse.hawkbit.ui.simple.view.AboutView;
import org.eclipse.hawkbit.ui.simple.view.ConfigView;
import org.eclipse.hawkbit.ui.simple.view.DistributionSetView;
import org.eclipse.hawkbit.ui.simple.view.RolloutView;
import org.eclipse.hawkbit.ui.simple.view.SoftwareModuleView;
import org.eclipse.hawkbit.ui.simple.view.TargetView;
/**
* The main view is a top-level placeholder for other views.
*/
public class MainLayout extends AppLayout {
private H2 viewTitle;
private final transient AuthenticatedUser authenticatedUser;
private final AccessAnnotationChecker accessChecker;
private H2 viewTitle;
public MainLayout(final AuthenticatedUser authenticatedUser, final AccessAnnotationChecker accessChecker) {
this.authenticatedUser = authenticatedUser;
@@ -62,6 +61,15 @@ public class MainLayout extends AppLayout {
addHeaderContent();
}
@Override
protected void afterNavigation() {
super.afterNavigation();
viewTitle.setText(
Optional.ofNullable(getContent().getClass().getAnnotation(PageTitle.class))
.map(PageTitle::value)
.orElse(""));
}
private void addHeaderContent() {
final DrawerToggle toggle = new DrawerToggle();
toggle.setAriaLabel("Menu toggle");
@@ -143,13 +151,4 @@ public class MainLayout extends AppLayout {
return layout;
}
@Override
protected void afterNavigation() {
super.afterNavigation();
viewTitle.setText(
Optional.ofNullable(getContent().getClass().getAnnotation(PageTitle.class))
.map(PageTitle::value)
.orElse(""));
}
}

View File

@@ -9,6 +9,13 @@
*/
package org.eclipse.hawkbit.ui.simple;
import static feign.Util.ISO_8859_1;
import java.util.Base64;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.server.PWA;
import com.vaadin.flow.theme.Theme;
@@ -35,15 +42,8 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Base64;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import static feign.Util.ISO_8859_1;
@Theme(themeClass = Lumo.class)
@PWA(name="hawkBit UI", shortName="hawkBit UI")
@PWA(name = "hawkBit UI", shortName = "hawkBit UI")
@SpringBootApplication
@Import(FeignClientsConfiguration.class)
public class SimpleUIApp implements AppShellConfigurator {
@@ -74,8 +74,8 @@ public class SimpleUIApp implements AppShellConfigurator {
hawkBitServer, client, encoder, decoder, contract,
ERROR_DECODER,
(tenant, controller) ->
controller == null ?
AUTHORIZATION : HawkbitClient.DEFAULT_REQUEST_INTERCEPTOR_FN.apply(tenant, controller));
controller == null ?
AUTHORIZATION : HawkbitClient.DEFAULT_REQUEST_INTERCEPTOR_FN.apply(tenant, controller));
}
@Bean
@@ -86,45 +86,46 @@ public class SimpleUIApp implements AppShellConfigurator {
// accepts all user / pass, just delegating them to the feign client
@Bean
AuthenticationManager authenticationManager(final HawkbitMgmtClient hawkbitClient) {
return authentication-> {
final String username = authentication.getName();
final String password = authentication.getCredentials().toString();
return authentication -> {
final String username = authentication.getName();
final String password = authentication.getCredentials().toString();
final List<String> roles = new LinkedList<>();
roles.add("ANONYMOUS");
final SecurityContext unauthorizedContext = SecurityContextHolder.createEmptyContext();
unauthorizedContext.setAuthentication(
new UsernamePasswordAuthenticationToken(username, password));
final SecurityContext currentContext = SecurityContextHolder.getContext();
try {
SecurityContextHolder.setContext(unauthorizedContext);
if (hawkbitClient.hasSoftwareModulesRead()) {
roles.add("SOFTWARE_MODULE_READ");
}
if (hawkbitClient.hasRolloutRead()) {
roles.add("ROLLOUT_READ");
}
if (hawkbitClient.hasDistributionSetRead()) {
roles.add("DISTRIBUTION_SET_READ");
}
if (hawkbitClient.hasTargetRead()) {
roles.add("TARGET_READ");
}
if (hawkbitClient.hasConfigRead()) {
roles.add("CONFIG_READ");
}
} finally {
SecurityContextHolder.setContext(currentContext);
final List<String> roles = new LinkedList<>();
roles.add("ANONYMOUS");
final SecurityContext unauthorizedContext = SecurityContextHolder.createEmptyContext();
unauthorizedContext.setAuthentication(
new UsernamePasswordAuthenticationToken(username, password));
final SecurityContext currentContext = SecurityContextHolder.getContext();
try {
SecurityContextHolder.setContext(unauthorizedContext);
if (hawkbitClient.hasSoftwareModulesRead()) {
roles.add("SOFTWARE_MODULE_READ");
}
if (hawkbitClient.hasRolloutRead()) {
roles.add("ROLLOUT_READ");
}
if (hawkbitClient.hasDistributionSetRead()) {
roles.add("DISTRIBUTION_SET_READ");
}
if (hawkbitClient.hasTargetRead()) {
roles.add("TARGET_READ");
}
if (hawkbitClient.hasConfigRead()) {
roles.add("CONFIG_READ");
}
} finally {
SecurityContextHolder.setContext(currentContext);
}
return new UsernamePasswordAuthenticationToken(
username, password,
roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).toList()) {
@Override
public void eraseCredentials() {
// don't erase credentials because they will be used
// to authenticate to the hawkBit update server / mgmt server
}
return new UsernamePasswordAuthenticationToken(
username, password,
roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).toList()) {
@Override
public void eraseCredentials() {
// don't erase credentials because they will be used
// to authenticate to the hawkBit update server / mgmt server
}
};
};
};
}
}

View File

@@ -9,13 +9,14 @@
*/
package org.eclipse.hawkbit.ui.simple.security;
import java.util.Optional;
import com.vaadin.flow.spring.security.AuthenticationContext;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class AuthenticatedUser {
private final AuthenticationContext authenticationContext;
public AuthenticatedUser(final AuthenticationContext authenticationContext) {

View File

@@ -9,8 +9,8 @@
*/
package org.eclipse.hawkbit.ui.simple.security;
import org.eclipse.hawkbit.ui.simple.view.LoginView;
import com.vaadin.flow.spring.security.VaadinWebSecurity;
import org.eclipse.hawkbit.ui.simple.view.LoginView;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -33,7 +33,7 @@ public class SecurityConfiguration extends VaadinWebSecurity {
http.authorizeHttpRequests(
authorize -> authorize.requestMatchers(new AntPathRequestMatcher("/images/*.png")).permitAll());
super.configure(http);
super.configure(http);
setLoginView(http, LoginView.class);
}
}

View File

@@ -9,7 +9,8 @@
*/
package org.eclipse.hawkbit.ui.simple.view;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
@@ -17,12 +18,12 @@ import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteAlias;
import com.vaadin.flow.theme.lumo.LumoUtility.Margin;
import jakarta.annotation.security.RolesAllowed;
import org.eclipse.hawkbit.ui.simple.MainLayout;
@PageTitle("About")
@Route(value = "about", layout = MainLayout.class)
@RouteAlias(value = "", layout = MainLayout.class)
@RolesAllowed({"ANONYMOUS"})
@RolesAllowed({ "ANONYMOUS" })
public class AboutView extends VerticalLayout {
public AboutView() {

View File

@@ -12,21 +12,21 @@ package org.eclipse.hawkbit.ui.simple.view;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationValueRequest;
import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationValueRequest;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.MainLayout;
@PageTitle("Config")
@Route(value = "config", layout = MainLayout.class)
@@ -34,58 +34,59 @@ import jakarta.annotation.security.RolesAllowed;
@Slf4j
public class ConfigView extends VerticalLayout {
private static final String WIDTH = "width";
private static final String PX_300 = "300px";
private static final String WIDTH = "width";
private static final String PX_300 = "300px";
private final Map<String, MgmtSystemTenantConfigurationValueRequest> configValue = new HashMap<>();
private final Map<String, MgmtSystemTenantConfigurationValueRequest> configValue = new HashMap<>();
public ConfigView(final HawkbitMgmtClient hawkbitClient) {
setSpacing(false);
final Button saveButton = new Button("Save");
hawkbitClient.getTenantManagementRestApi().getTenantConfiguration().getBody().forEach((k, v) -> {
Component value = null;
if (v.getValue() instanceof String strValue) {
TextField tf = new TextField(k, strValue, event -> {
final MgmtSystemTenantConfigurationValueRequest vre = new MgmtSystemTenantConfigurationValueRequest();
vre.setValue(event.getValue());
configValue.put(k, vre);
});
tf.getElement().getStyle().set(WIDTH, PX_300);
value = tf;
} else if (v.getValue() instanceof Boolean boolValue) {
value = new Checkbox(k, boolValue, event -> {
final MgmtSystemTenantConfigurationValueRequest vre = new MgmtSystemTenantConfigurationValueRequest();
vre.setValue(event.getValue());
configValue.put(k, vre);
});
} else if (v.getValue() instanceof Long longValue) {
final NumberField nf = new NumberField(k, (double) longValue, event -> {
final MgmtSystemTenantConfigurationValueRequest vre = new MgmtSystemTenantConfigurationValueRequest();
vre.setValue(event.getValue());
configValue.put(k, vre);
});
nf.getElement().getStyle().set(WIDTH, PX_300);
value = nf;
} else if (v.getValue() instanceof Integer intValue) {
final NumberField nf = new NumberField(k, (double) intValue, event -> {
MgmtSystemTenantConfigurationValueRequest vre = new MgmtSystemTenantConfigurationValueRequest();
vre.setValue(event.getValue());
configValue.put(k, vre);
});
nf.getElement().getStyle().set(WIDTH, PX_300);
value = nf;
} else {
log.debug("Unexpected value type: {} -> {} (class: {})", k, v.getValue(), v.getValue() == null ? "null" : v.getValue().getClass());
}
if (value != null) {
add(value);
}
});
public ConfigView(final HawkbitMgmtClient hawkbitClient) {
setSpacing(false);
final Button saveButton = new Button("Save");
hawkbitClient.getTenantManagementRestApi().getTenantConfiguration().getBody().forEach((k, v) -> {
Component value = null;
if (v.getValue() instanceof String strValue) {
TextField tf = new TextField(k, strValue, event -> {
final MgmtSystemTenantConfigurationValueRequest vre = new MgmtSystemTenantConfigurationValueRequest();
vre.setValue(event.getValue());
configValue.put(k, vre);
});
tf.getElement().getStyle().set(WIDTH, PX_300);
value = tf;
} else if (v.getValue() instanceof Boolean boolValue) {
value = new Checkbox(k, boolValue, event -> {
final MgmtSystemTenantConfigurationValueRequest vre = new MgmtSystemTenantConfigurationValueRequest();
vre.setValue(event.getValue());
configValue.put(k, vre);
});
} else if (v.getValue() instanceof Long longValue) {
final NumberField nf = new NumberField(k, (double) longValue, event -> {
final MgmtSystemTenantConfigurationValueRequest vre = new MgmtSystemTenantConfigurationValueRequest();
vre.setValue(event.getValue());
configValue.put(k, vre);
});
nf.getElement().getStyle().set(WIDTH, PX_300);
value = nf;
} else if (v.getValue() instanceof Integer intValue) {
final NumberField nf = new NumberField(k, (double) intValue, event -> {
MgmtSystemTenantConfigurationValueRequest vre = new MgmtSystemTenantConfigurationValueRequest();
vre.setValue(event.getValue());
configValue.put(k, vre);
});
nf.getElement().getStyle().set(WIDTH, PX_300);
value = nf;
} else {
log.debug("Unexpected value type: {} -> {} (class: {})", k, v.getValue(),
v.getValue() == null ? "null" : v.getValue().getClass());
}
if (value != null) {
add(value);
}
});
saveButton.addClickListener(click ->
configValue.forEach((key, value) ->
hawkbitClient.getTenantManagementRestApi().updateTenantConfigurationValue(key, value)));
saveButton.addClickShortcut(Key.ENTER);
add(saveButton);
}
saveButton.addClickListener(click ->
configValue.forEach((key, value) ->
hawkbitClient.getTenantManagementRestApi().updateTenantConfigurationValue(key, value)));
saveButton.addClickShortcut(Key.ENTER);
add(saveButton);
}
}

View File

@@ -9,12 +9,17 @@
*/
package org.eclipse.hawkbit.ui.simple.view;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.view.util.Filter;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid;
import org.eclipse.hawkbit.ui.simple.view.util.TableView;
import org.eclipse.hawkbit.ui.simple.view.util.Utils;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
@@ -33,26 +38,22 @@ import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.RolesAllowed;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetType;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleAssigment;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import org.eclipse.hawkbit.ui.simple.view.util.Filter;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid;
import org.eclipse.hawkbit.ui.simple.view.util.TableView;
import org.eclipse.hawkbit.ui.simple.view.util.Utils;
@PageTitle("Distribution Sets")
@Route(value = "distribution_sets", layout = MainLayout.class)
@RolesAllowed({"DISTRIBUTION_SET_READ"})
@RolesAllowed({ "DISTRIBUTION_SET_READ" })
@Uses(Icon.class)
public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
@@ -93,6 +94,7 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
return new SelectionGrid<>(
new SelectionGrid.EntityRepresentation<>(
MgmtSoftwareModule.class, MgmtSoftwareModule::getModuleId) {
@Override
protected void addColumns(Grid<MgmtSoftwareModule> grid) {
grid.addColumn(MgmtSoftwareModule::getModuleId).setHeader(Constants.ID).setAutoWidth(true);
@@ -158,9 +160,9 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
description.setMinLength(2);
Stream.of(
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt)
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt)
.forEach(field -> {
field.setReadOnly(true);
add(field);
@@ -258,17 +260,17 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
close();
final long distributionSetId = hawkbitClient.getDistributionSetRestApi()
.createDistributionSets(
List.of((MgmtDistributionSetRequestBodyPost)new MgmtDistributionSetRequestBodyPost()
.setType(type.getValue().getKey())
.setName(name.getValue())
.setVersion(version.getValue())
.setDescription(description.getValue())
.setRequiredMigrationStep(requiredMigrationStep.getValue())))
.getBody()
.stream()
.findFirst()
.orElseThrow()
.getDsId();
List.of((MgmtDistributionSetRequestBodyPost) new MgmtDistributionSetRequestBodyPost()
.setType(type.getValue().getKey())
.setName(name.getValue())
.setVersion(version.getValue())
.setDescription(description.getValue())
.setRequiredMigrationStep(requiredMigrationStep.getValue())))
.getBody()
.stream()
.findFirst()
.orElseThrow()
.getDsId();
new AddSoftwareModulesDialog(distributionSetId, hawkbitClient).open();
});
}
@@ -289,17 +291,17 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
final Component addRemoveControls = Utils.addRemoveControls(
v -> new Utils.BaseDialog<Void>("Add Software Modules") {{
final SoftwareModuleView softwareModulesView = new SoftwareModuleView(false, hawkbitClient);
add(softwareModulesView);
final Button addBtn = new Button("Add");
addBtn.addClickListener(e -> {
softwareModules.addAll(softwareModulesView.getSelection());
softwareModulesGrid.refreshGrid(false);
close();
});
add(addBtn);
open();
}}.result(),
final SoftwareModuleView softwareModulesView = new SoftwareModuleView(false, hawkbitClient);
add(softwareModulesView);
final Button addBtn = new Button("Add");
addBtn.addClickListener(e -> {
softwareModules.addAll(softwareModulesView.getSelection());
softwareModulesGrid.refreshGrid(false);
close();
});
add(addBtn);
open();
}}.result(),
v -> {
Utils.remove(softwareModulesGrid.getSelectedItems(), softwareModules, MgmtSoftwareModule::getModuleId);
softwareModulesGrid.refreshGrid(false);

View File

@@ -9,7 +9,6 @@
*/
package org.eclipse.hawkbit.ui.simple.view;
import org.eclipse.hawkbit.ui.simple.security.AuthenticatedUser;
import com.vaadin.flow.component.login.LoginI18n;
import com.vaadin.flow.component.login.LoginOverlay;
import com.vaadin.flow.router.BeforeEnterEvent;
@@ -19,6 +18,7 @@ import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.internal.RouteUtil;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import org.eclipse.hawkbit.ui.simple.security.AuthenticatedUser;
@AnonymousAllowed
@PageTitle("Login")

View File

@@ -9,17 +9,20 @@
*/
package org.eclipse.hawkbit.ui.simple.view;
import com.vaadin.flow.component.checkbox.Checkbox;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid;
import org.eclipse.hawkbit.ui.simple.view.util.TableView;
import org.eclipse.hawkbit.ui.simple.view.util.Utils;
import org.eclipse.hawkbit.ui.simple.view.util.Filter;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.datetimepicker.DateTimePicker;
import com.vaadin.flow.component.dependency.Uses;
import com.vaadin.flow.component.formlayout.FormLayout;
@@ -37,7 +40,6 @@ import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.RolesAllowed;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutCondition;
@@ -46,18 +48,17 @@ import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutRestRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroupResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import org.eclipse.hawkbit.ui.simple.view.util.Filter;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid;
import org.eclipse.hawkbit.ui.simple.view.util.TableView;
import org.eclipse.hawkbit.ui.simple.view.util.Utils;
import org.springframework.util.ObjectUtils;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
@PageTitle("Rollouts")
@Route(value = "rollouts", layout = MainLayout.class)
@RolesAllowed({"ROLLOUT_READ"})
@RolesAllowed({ "ROLLOUT_READ" })
@Uses(Icon.class)
public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
@@ -81,9 +82,9 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
grid.addComponentColumn(rollout -> new Actions(rollout, grid, hawkbitClient)).setHeader(
Constants.ACTIONS).setAutoWidth(true);
grid.setItemDetailsRenderer(new ComponentRenderer<>(
() -> details, RolloutDetails::setItem));
}
grid.setItemDetailsRenderer(new ComponentRenderer<>(
() -> details, RolloutDetails::setItem));
}
},
(query, rsqlFilter) -> hawkbitClient.getRolloutRestApi()
.getRollouts(
@@ -103,6 +104,7 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
private static SelectionGrid<MgmtRolloutGroupResponseBody, Long> createGroupGrid() {
return new SelectionGrid<>(
new SelectionGrid.EntityRepresentation<>(MgmtRolloutGroupResponseBody.class, MgmtRolloutGroupResponseBody::getRolloutGroupId) {
@Override
protected void addColumns(final Grid<MgmtRolloutGroupResponseBody> grid) {
grid.addColumn(MgmtRolloutGroupResponseBody::getRolloutGroupId).setHeader(Constants.ID).setAutoWidth(true);
@@ -120,7 +122,8 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
private final Grid<MgmtRolloutResponseBody> grid;
private final transient HawkbitMgmtClient hawkbitClient;
private Actions(final MgmtRolloutResponseBody rollout, final Grid<MgmtRolloutResponseBody> grid, final HawkbitMgmtClient hawkbitClient) {
private Actions(final MgmtRolloutResponseBody rollout, final Grid<MgmtRolloutResponseBody> grid,
final HawkbitMgmtClient hawkbitClient) {
this.rolloutId = rollout.getRolloutId();
this.grid = grid;
this.hawkbitClient = hawkbitClient;
@@ -196,7 +199,7 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
private final TextField distributionSet = Utils.textField(Constants.DISTRIBUTION_SET);
private final TextField actonType = Utils.textField(Constants.ACTION_TYPE);
private final TextField startAt = Utils.textField(Constants.START_AT);
private final Checkbox dynamic = new Checkbox(Constants.DYNAMIC);
private final Checkbox dynamic = new Checkbox(Constants.DYNAMIC);
private final SelectionGrid<MgmtRolloutGroupResponseBody, Long> groupGrid;
private RolloutDetails(final HawkbitMgmtClient hawkbitClient) {
@@ -205,11 +208,11 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
description.setMinLength(2);
groupGrid = createGroupGrid();
Stream.of(
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt,
targetFilter, distributionSet,
actonType, startAt)
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt,
targetFilter, distributionSet,
actonType, startAt)
.forEach(field -> {
field.setReadOnly(true);
add(field);
@@ -258,10 +261,6 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
private static class CreateDialog extends Utils.BaseDialog<Void> {
private enum StartType {
MANUAL, AUTO, SCHEDULED
}
private final TextField name;
private final Select<MgmtDistributionSet> distributionSet;
private final Select<MgmtTargetFilterQuery> targetFilter;
@@ -274,7 +273,6 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
private final NumberField triggerThreshold;
private final NumberField errorThreshold;
private final Checkbox dynamic = new Checkbox(Constants.DYNAMIC);
private final Button create = new Button("Create");
private CreateDialog(final HawkbitMgmtClient hawkbitClient) {
@@ -443,5 +441,9 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
hawkbitClient.getRolloutRestApi().create(request).getBody();
});
}
private enum StartType {
MANUAL, AUTO, SCHEDULED
}
}
}

View File

@@ -9,12 +9,20 @@
*/
package org.eclipse.hawkbit.ui.simple.view;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid;
import org.eclipse.hawkbit.ui.simple.view.util.TableView;
import org.eclipse.hawkbit.ui.simple.view.util.Utils;
import org.eclipse.hawkbit.ui.simple.view.util.Filter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
@@ -35,30 +43,22 @@ import com.vaadin.flow.component.upload.receivers.FileBuffer;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.RolesAllowed;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import org.eclipse.hawkbit.ui.simple.view.util.Filter;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid;
import org.eclipse.hawkbit.ui.simple.view.util.TableView;
import org.eclipse.hawkbit.ui.simple.view.util.Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
@PageTitle("Software Modules")
@Route(value = "software_modules", layout = MainLayout.class)
@RolesAllowed({"SOFTWARE_MODULE_READ"})
@RolesAllowed({ "SOFTWARE_MODULE_READ" })
@Uses(Icon.class)
public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
@@ -73,6 +73,7 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
new SelectionGrid.EntityRepresentation<>(MgmtSoftwareModule.class, MgmtSoftwareModule::getModuleId) {
private final SoftwareModuleDetails details = new SoftwareModuleDetails(hawkbitClient);
@Override
protected void addColumns(final Grid<MgmtSoftwareModule> grid) {
grid.addColumn(MgmtSoftwareModule::getModuleId).setHeader(Constants.ID).setAutoWidth(true);
@@ -108,6 +109,7 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
private static SelectionGrid<MgmtArtifact, Long> createArtifactGrid() {
return new SelectionGrid<>(
new SelectionGrid.EntityRepresentation<>(MgmtArtifact.class, MgmtArtifact::getArtifactId) {
@Override
protected void addColumns(final Grid<MgmtArtifact> grid) {
grid.addColumn(MgmtArtifact::getArtifactId).setHeader(Constants.ID).setAutoWidth(true);
@@ -165,9 +167,9 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
description.setMinLength(2);
artifactGrid = createArtifactGrid();
Stream.of(
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt)
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt)
.forEach(field -> {
field.setReadOnly(true);
add(field);
@@ -260,7 +262,7 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
create.addClickListener(e -> {
close();
final long softwareModuleId = hawkbitClient.getSoftwareModuleRestApi().createSoftwareModules(
List.of(new MgmtSoftwareModuleRequestBodyPost()
List.of(new MgmtSoftwareModuleRequestBodyPost()
.setType(type.getValue().getKey())
.setName(name.getValue())
.setVersion(version.getValue())
@@ -308,7 +310,7 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
});
final Button finishBtn = Utils.tooltip(new Button("Finish"), "Finish (Enter)");
finishBtn.addClickListener(e -> close());
finishBtn.addClickListener(e -> close());
finishBtn.addClickShortcut(Key.ENTER);
finishBtn.setHeightFull();
final HorizontalLayout finish = new HorizontalLayout(finishBtn);
@@ -329,7 +331,7 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
public MultipartFileImpl(final FileBuffer fileBuffer, final long contentLength, final String mimeType) {
this.fileBuffer = fileBuffer;
this. contentLength = contentLength;
this.contentLength = contentLength;
this.mimeType = mimeType;
}

View File

@@ -9,12 +9,17 @@
*/
package org.eclipse.hawkbit.ui.simple.view;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.view.util.Filter;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid;
import org.eclipse.hawkbit.ui.simple.view.util.TableView;
import org.eclipse.hawkbit.ui.simple.view.util.Utils;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
@@ -33,27 +38,23 @@ import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.RolesAllowed;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.targettype.MgmtTargetType;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import org.eclipse.hawkbit.ui.simple.view.util.Filter;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid;
import org.eclipse.hawkbit.ui.simple.view.util.TableView;
import org.eclipse.hawkbit.ui.simple.view.util.Utils;
import org.springframework.util.ObjectUtils;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
@PageTitle("Targets")
@Route(value = "targets", layout = MainLayout.class)
@RolesAllowed({"TARGET_READ"})
@RolesAllowed({ "TARGET_READ" })
@Uses(Icon.class)
public class TargetView extends TableView<MgmtTarget, String> {
@@ -156,34 +157,35 @@ public class TargetView extends TableView<MgmtTarget, String> {
.getFilters(0, 30, null, null, null)
.getBody().getContent())
.orElse(Collections.emptyList()));
savedFilters.setItemLabelGenerator(query -> Optional.ofNullable(query).map(MgmtTargetFilterQuery::getName).orElse("<select saved filter>"));
savedFilters.setItemLabelGenerator(
query -> Optional.ofNullable(query).map(MgmtTargetFilterQuery::getName).orElse("<select saved filter>"));
savedFilters.setWidthFull();
textFilter.setWidthFull();
final Button saveBtn = Utils.tooltip(new Button(VaadinIcon.ARCHIVE.create()), "Save (Enter)");
saveBtn.addClickListener(e ->
new Utils.BaseDialog<Void>("Save Filter") {{
setHeight("40%");
final Button finishBtn = Utils.tooltip(new Button("Save"), "Save (Enter)");
final TextField name = Utils.textField(
Constants.NAME,
e -> finishBtn.setEnabled(!e.getHasValue().isEmpty()));
name.focus();
finishBtn.addClickShortcut(Key.ENTER);
finishBtn.setEnabled(false);
finishBtn.addClickListener(e -> {
final MgmtTargetFilterQueryRequestBody createRequest = new MgmtTargetFilterQueryRequestBody();
createRequest.setName(name.getValue());
createRequest.setQuery(textFilter.getValue());
hawkbitClient.getTargetFilterQueryRestApi().createFilter(createRequest);
savedFilters.setItems(
hawkbitClient.getTargetFilterQueryRestApi()
.getFilters(0, 30, null, null, null).getBody().getContent());
close();
});
add(name, finishBtn);
open();
}});
new Utils.BaseDialog<Void>("Save Filter") {{
setHeight("40%");
final Button finishBtn = Utils.tooltip(new Button("Save"), "Save (Enter)");
final TextField name = Utils.textField(
Constants.NAME,
e -> finishBtn.setEnabled(!e.getHasValue().isEmpty()));
name.focus();
finishBtn.addClickShortcut(Key.ENTER);
finishBtn.setEnabled(false);
finishBtn.addClickListener(e -> {
final MgmtTargetFilterQueryRequestBody createRequest = new MgmtTargetFilterQueryRequestBody();
createRequest.setName(name.getValue());
createRequest.setQuery(textFilter.getValue());
hawkbitClient.getTargetFilterQueryRestApi().createFilter(createRequest);
savedFilters.setItems(
hawkbitClient.getTargetFilterQueryRestApi()
.getFilters(0, 30, null, null, null).getBody().getContent());
close();
});
add(name, finishBtn);
open();
}});
saveBtn.addClickShortcut(Key.ENTER);
layout.setSpacing(false);
@@ -216,10 +218,10 @@ public class TargetView extends TableView<MgmtTarget, String> {
private TargetDetails() {
description.setMinLength(2);
Stream.of(
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt,
securityToken)
description,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt,
securityToken)
.forEach(field -> {
field.setReadOnly(true);
add(field);
@@ -300,7 +302,7 @@ public class TargetView extends TableView<MgmtTarget, String> {
request.setTargetType(type.getValue().getTypeId());
}
hawkbitClient.getTargetRestApi().createTargets(
List.of(request))
List.of(request))
.getBody()
.stream()
.findFirst()

View File

@@ -9,6 +9,15 @@
*/
package org.eclipse.hawkbit.ui.simple.view.util;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasValue;
import com.vaadin.flow.component.button.Button;
@@ -19,15 +28,6 @@ import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.theme.lumo.LumoUtility;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Filter extends Div {
private transient Rsql rsql;
@@ -46,7 +46,7 @@ public class Filter extends Div {
filtersDiv.add(primaryRsql.components());
filtersDiv.addClassName(LumoUtility.Gap.SMALL);
final Button searchBtn = Utils.tooltip(new Button(VaadinIcon.REFRESH.create()),"Search");
final Button searchBtn = Utils.tooltip(new Button(VaadinIcon.REFRESH.create()), "Search");
searchBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
searchBtn.addClickListener(e -> changeListener.accept(rsql.filter()));
final Button resetBtn = Utils.tooltip(new Button(VaadinIcon.ERASER.create()), "Reset");

View File

@@ -9,11 +9,6 @@
*/
package org.eclipse.hawkbit.ui.simple.view.util;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.theme.lumo.LumoUtility;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@@ -21,12 +16,16 @@ import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.theme.lumo.LumoUtility;
// id type shall have proper equals and hashCode - i.e. eligible hash set element
public class SelectionGrid<T,ID> extends Grid<T> {
public class SelectionGrid<T, ID> extends Grid<T> {
private volatile String rsqlFilter;
public SelectionGrid(
final EntityRepresentation<T, ID> entityRepresentation) {
this(entityRepresentation, null);

View File

@@ -9,16 +9,16 @@
*/
package org.eclipse.hawkbit.ui.simple.view.util;
import org.eclipse.hawkbit.ui.simple.view.Constants;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.data.provider.Query;
import java.util.concurrent.CompletionStage;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.data.provider.Query;
import org.eclipse.hawkbit.ui.simple.view.Constants;
public class TableView<T, ID> extends Div implements Constants {
protected SelectionGrid<T, ID> selectionGrid;
@@ -30,12 +30,14 @@ public class TableView<T, ID> extends Div implements Constants {
final BiFunction<Query<T, Void>, String, Stream<T>> queryFn) {
this(rsql, null, entityRepresentation, queryFn, null, null);
}
public TableView(
final Filter.Rsql rsql, final Filter.Rsql alternativeRsql,
final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation,
final BiFunction<Query<T, Void>, String, Stream<T>> queryFn) {
this(rsql, alternativeRsql, entityRepresentation, queryFn,null, null);
this(rsql, alternativeRsql, entityRepresentation, queryFn, null, null);
}
public TableView(
final Filter.Rsql rsql,
final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation,
@@ -44,6 +46,7 @@ public class TableView<T, ID> extends Div implements Constants {
final Function<SelectionGrid<T, ID>, CompletionStage<Void>> removeHandler) {
this(rsql, null, entityRepresentation, queryFn, addHandler, removeHandler);
}
public TableView(
final Filter.Rsql rsql, final Filter.Rsql alternativeRsql,
final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation,
@@ -55,7 +58,7 @@ public class TableView<T, ID> extends Div implements Constants {
setSizeFull();
final VerticalLayout layout = new VerticalLayout(filter, selectionGrid);
final VerticalLayout layout = new VerticalLayout(filter, selectionGrid);
layout.setSizeFull();
layout.setPadding(false);
layout.setSpacing(false);

View File

@@ -9,6 +9,14 @@
*/
package org.eclipse.hawkbit.ui.simple.view.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
import java.util.function.Function;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasValue;
import com.vaadin.flow.component.Text;
@@ -28,14 +36,6 @@ import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.theme.lumo.LumoUtility;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
import java.util.function.Function;
public class Utils {
private Utils() {
@@ -45,6 +45,7 @@ public class Utils {
public static TextField textField(final String label) {
return textField(label, null);
}
public static TextField textField(final String label, final Consumer<HasValue.ValueChangeEvent<String>> changeListener) {
final TextField textField = new TextField(label);
textField.setWidthFull();
@@ -60,6 +61,7 @@ public class Utils {
public static NumberField numberField(final String label) {
return numberField(label, null);
}
public static NumberField numberField(final String label, final Consumer<HasValue.ValueChangeEvent<Double>> changeListener) {
final NumberField numberField = new NumberField(label);
numberField.setWidthFull();
@@ -109,7 +111,7 @@ public class Utils {
public static <T> void remove(final Collection<T> remove, final Set<T> from, final Function<T, ?> idFn) {
remove.forEach(toRemove -> {
final Object id = idFn.apply(toRemove);
for (final Iterator<T> i = from.iterator(); i.hasNext();) {
for (final Iterator<T> i = from.iterator(); i.hasNext(); ) {
if (idFn.apply(i.next()).equals(id)) {
i.remove();
}

View File

@@ -19,11 +19,11 @@ logging.pattern.console=%clr(%d{${logging.pattern.dateformat:yyyy-MM-dd'T'HH:mm:
### Vaadin start ###`
# build with mvn vaadin:build-frontend to enable / disable
vaadin.frontend.hotdeploy=false
logging.level.org.atmosphere = warn
spring.mustache.check-template-location = false
logging.level.org.atmosphere=warn
spring.mustache.check-template-location=false
# Launch the default browser when starting the application in development mode
vaadin.launch-browser=true
# To improve the performance during development.
# For more information https://vaadin.com/docs/flow/spring/tutorial-spring-configuration.html#special-configuration-parameters
vaadin.whitelisted-packages = com.vaadin,org.vaadin,dev.hilla,org.eclipse.hawkbit
vaadin.whitelisted-packages=com.vaadin,org.vaadin,dev.hilla,org.eclipse.hawkbit
### Vaadin end ###

View File

@@ -41,7 +41,8 @@ Add to your `pom.xml` :
</dependency>
```
Optional as well is the addition of [Protostuff](https://github.com/protostuff/protostuff) based message payload serialization for improved performance.
Optional as well is the addition of [Protostuff](https://github.com/protostuff/protostuff) based message payload
serialization for improved performance.
Add to your `application.properties` :

View File

@@ -9,7 +9,7 @@
SPDX-License-Identifier: EPL-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>

View File

@@ -37,10 +37,8 @@ public class ErrorController extends BasicErrorController {
/**
* A new {@link ErrorController}.
*
* @param errorAttributes
* the error attributes
* @param serverProperties
* configuration properties
* @param errorAttributes the error attributes
* @param serverProperties configuration properties
*/
public ErrorController(final ErrorAttributes errorAttributes, final ServerProperties serverProperties) {
super(errorAttributes, serverProperties.getError());

View File

@@ -22,7 +22,6 @@ import org.springframework.web.servlet.view.RedirectView;
/**
* A {@link SpringBootApplication} annotated class with a main method to start.
* The minimal configuration for the stand alone hawkBit server.
*
*/
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
@EnableHawkbitManagedSecurityConfiguration

View File

@@ -24,11 +24,10 @@ import org.springframework.web.context.WebApplicationContext;
@ExtendWith(SharedSqlTestDatabaseExtension.class)
public abstract class AbstractSecurityTest {
protected MockMvc mvc;
@Autowired
private WebApplicationContext context;
protected MockMvc mvc;
@BeforeEach
public void setup() {
final DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context)

View File

@@ -9,17 +9,14 @@
*/
package org.eclipse.hawkbit.app;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import io.qameta.allure.Description;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.security.web.firewall.RequestRejectedException;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = { "hawkbit.server.security.allowedHostNames=localhost",

View File

@@ -14,19 +14,17 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.eclipse.hawkbit.im.authentication.SpRole;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.test.web.servlet.ResultActions;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
@SpringBootTest(
properties = {
"hawkbit.dmf.rabbitmq.enabled=false",
@@ -35,7 +33,7 @@ import io.qameta.allure.Story;
CorsTest.ALLOWED_ORIGIN_FIRST + "," +
CorsTest.ALLOWED_ORIGIN_SECOND,
"hawkbit.server.security.cors.exposedHeaders=" +
HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN})
HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN })
@Feature("Integration Test - Security")
@Story("CORS")
public class CorsTest extends AbstractSecurityTest {
@@ -63,8 +61,8 @@ public class CorsTest extends AbstractSecurityTest {
final String invalidCorsUrlResponseBody = performOptionsRequestToUrlWithOrigin(
MgmtRestConstants.BASE_SYSTEM_MAPPING, ALLOWED_ORIGIN_FIRST).andExpect(status().isForbidden())
.andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).andReturn()
.getResponse().getContentAsString();
.andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).andReturn()
.getResponse().getContentAsString();
assertThat(invalidCorsUrlResponseBody).isEqualTo(INVALID_CORS_REQUEST);
}

View File

@@ -9,6 +9,11 @@
*/
package org.eclipse.hawkbit.app;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import java.util.HashMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
@@ -19,26 +24,21 @@ import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import java.util.HashMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@Feature("Integration Test - Security")
@Story("PreAuthorized enabled")
public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test
@Description("Tests whether request fail if a role is forbidden for the user")
@WithUser(authorities = { SpPermission.READ_TARGET } )
@WithUser(authorities = { SpPermission.READ_TARGET })
public void failIfNoRole() throws Exception {
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result ->
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()));
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()));
}
@Test
@Description("Tests whether request succeed if a role is granted for the user")
@WithUser(authorities = { SpPermission.READ_REPOSITORY })
@WithUser(authorities = { SpPermission.READ_REPOSITORY })
public void successIfHasRole() throws Exception {
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> {
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
@@ -47,7 +47,7 @@ public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test
@Description("Tests whether request succeed if a role is granted for the user")
@WithUser(authorities = { SpRole.TENANT_ADMIN })
@WithUser(authorities = { SpRole.TENANT_ADMIN })
public void successIfHasTenantAdminRole() throws Exception {
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> {
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
@@ -57,7 +57,7 @@ public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test
@Description("Tests whether read tenant config request fail if a tenant config (or read read) is not " +
"granted for the user")
@WithUser(authorities = { SpPermission.READ_TARGET } )
@WithUser(authorities = { SpPermission.READ_TARGET })
public void onlyDSIfNoTenantConfig() throws Exception {
mvc.perform(get("/rest/v1/system/configs")).andExpect(result -> {
// returns default DS type because of READ_TARGET
@@ -72,7 +72,7 @@ public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test
@Description("Tests whether read tenant config request succeed if a tenant config (not read explicitly) is " +
"granted for the user")
@WithUser(authorities = { SpPermission.TENANT_CONFIGURATION })
@WithUser(authorities = { SpPermission.TENANT_CONFIGURATION })
public void successIfHasTenantConfig() throws Exception {
mvc.perform(get("/rest/v1/system/configs")).andExpect(result ->
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));

View File

@@ -9,96 +9,97 @@
SPDX-License-Identifier: EPL-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-parent</artifactId>
<version>${revision}</version>
</parent>
<parent>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-parent</artifactId>
<version>${revision}</version>
</parent>
<artifactId>hawkbit-runtime-parent</artifactId>
<name>hawkBit :: Runtime :: Parent</name>
<packaging>pom</packaging>
<artifactId>hawkbit-runtime-parent</artifactId>
<name>hawkBit :: Runtime :: Parent</name>
<packaging>pom</packaging>
<!-- Common runtime dependences -->
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- Common runtime dependences -->
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-test</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- Test -->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<outputDirectory>${baseDir}</outputDirectory>
<mainClass>${spring.app.class}</mainClass>
<layout>JAR</layout>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-test</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<outputDirectory>${baseDir}</outputDirectory>
<mainClass>${spring.app.class}</mainClass>
<layout>JAR</layout>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<modules>
<module>hawkbit-ddi-server</module>
<module>hawkbit-dmf-server</module>
<module>hawkbit-mgmt-server</module>
<module>hawkbit-simple-ui</module>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
<module>hawkbit-update-server</module>
</modules>
<modules>
<module>hawkbit-ddi-server</module>
<module>hawkbit-dmf-server</module>
<module>hawkbit-mgmt-server</module>
<module>hawkbit-simple-ui</module>
<module>hawkbit-update-server</module>
</modules>
</project>