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 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 - try out DDI API https://hawkbit.eclipseprojects.io/DEFAULT/controller/v1/MYTESTDEVICE
## Sandbox Setup ## Sandbox Setup
**1. File Structure** **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 $ sudo /.sandbox/scripts/initialize-cronjobs.sh
``` ```
**3. Nginx Reverse Proxy** **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 Start the stack for the Nginx reverse proxy with Let's Encrypt support. Thanks to JrCs for providing the Docker
and instructions with his [Docker-Letsencrypt-Nginx-Companion](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion). 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 $ docker stack deploy -c /.sandbox/stacks/sandbox/docker-compose-stack.yml proxy

View File

@@ -1,9 +1,8 @@
hawkBit Runtime hawkBit Runtime
=== ===
| Folder | Description | | Folder | Description |
|--------------------------|----------------------------------------------------------------------------------------------------------------------------| |--------------------------|------------------------------------------------------------------------------------------------------------------------------|
| `.sandbox/` | Content of the hawkBit sandbox installation running on [hawkbit.eclipseprojects.io](https://hawkbit.eclipseprojects.io/UI/). | | `.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 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. | | `docker/docker_build/` | Docker images build related files, such es Dockerfiles and build shell scripts. |
@@ -12,4 +11,5 @@ hawkBit Runtime
| `hawkbit-dmf-server/` | Spring-Boot application of hawkBit DMF 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 | | `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 $ docker-compose up -d
``` ```
## C: Docker Stack ## 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 ```bash
$ docker swarm init $ docker swarm init
@@ -33,7 +33,7 @@ $ docker stack deploy -c docker-compose-deps-mysql.yml hawkbit
# Access # Access
| Service / Container | URL | Login | A | B | C | | Service / Container | URL | Login | A | B | C |
|---|---|---|---|---|---| |--------------------------|--------------------------------------------------|-------------|----------|----------|----------|
| hawkBit Update Server | [http://localhost:8080/](http://localhost:8080/) | admin:admin | &#10003; | &#10003; | &#10003; | | hawkBit Update Server | [http://localhost:8080/](http://localhost:8080/) | admin:admin | &#10003; | &#10003; | &#10003; |
| hawkBit Device Simulator | [http://localhost:8083/](http://localhost:8083/) | - | | | &#10003; | | hawkBit Device Simulator | [http://localhost:8083/](http://localhost:8083/) | - | | | &#10003; |
| MySQL | localhost:3306/hawkbit | root | | &#10003; | &#10003; | | MySQL | localhost:3306/hawkbit | root | | &#10003; | &#10003; |
@@ -41,7 +41,9 @@ $ docker stack deploy -c docker-compose-deps-mysql.yml hawkbit
# Configuration # 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: hawkbit:
image: "hawkbit/hawkbit-update-server:latest-mysql" image: "hawkbit/hawkbit-update-server:latest-mysql"

View File

@@ -1,55 +1,82 @@
hawkBit Docker Build hawkBit Docker Build
=== ===
This directory contains docker files for building both hawkBit docker flavours: This directory contains docker files for building both hawkBit docker flavours:
* _standard_ - hawkBit images without mysql driver * _standard_ - hawkBit images without mysql driver
* _mysql_ - with MariaDB Java connector with support for MySQL. * _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: For every flavour there are two build types:
* _release_ - uses officially released hawkBit versions, downloading them from https://repo1.maven.org * _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 ## Build overview
Building images supports the following build arguments (i.e. ARG-s which could be passed using _--build-arg_): 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. * _JAVA_VERSION_ - **[OPTIONAL, if not set a default is used]** the Java version of the eclipse-temurin jre-alpine base
* _HAWKBIT_VERSION_ - **[OPTIONAL, if not set a default, should be the last officially released version, is used]** the application version 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) * _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 Additionally, tge _development_ builds shall be started with docker build context the local maven repository
## Build standard ## Build standard
Standard flavour could be build, for example, with (fixed version 0.4.1 is just an example): Standard flavour could be build, for example, with (fixed version 0.4.1 is just an example):
```shell ```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 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: or just by:
```shell ```shell
docker build --build-arg HAWKBIT_VERSION=0.4.1 -t hawkbit_update_server:0.4.1 . 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_. 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: To build standard development docker images, e.g. snapshot based, you could use something like:
```shell ```shell
docker build -t hawkbit_update_server:0-SNAPSHOT -f Dockerfile_dev ~/.m2/repository 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 ## Build mysql
Mysql flavour could be build, for example, with: Mysql flavour could be build, for example, with:
```shell ```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 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: or just by:
```shell ```shell
docker build --build-arg -t hawkbit_update_server:0.4.1-mysql --build-arg HAWKBIT_VERSION=0.4.1 . -f Dockerfile-mysql 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_. 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: To build development mysql docker images, e.g. snapshot based, you could use something like:
```shell ```shell
docker build -t hawkbit_update_server:0-SNAPSHOT-mysql -f Dockerfile_dev-mysql ~/.m2/repository 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!) # 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 ## On your own workstation
@@ -18,14 +20,22 @@ run org.eclipse.hawkbit.app.ddi.DDIStart
``` ```
### Usage ### Usage
The Management API can be accessed via http://localhost:8081/rest/v1 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 The root url http://localhost:8081 will redirect directly to the Swagger Management UI
### Clustering (Experimental!!!) ### 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 ## 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! **Note**: If Protostuff is enabled it shall be enabled on all microservices!

View File

@@ -9,7 +9,7 @@
SPDX-License-Identifier: EPL-2.0 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"> 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> <modelVersion>4.0.0</modelVersion>
<parent> <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.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.servlet.view.RedirectView;
/** /**
* A {@link SpringBootApplication} annotated class with a main method to start. * A {@link SpringBootApplication} annotated class with a main method to start.
* The minimal configuration for the stand alone hawkBit DDI server. * 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. * Main method to start the spring-boot application.
* *
* @param args * @param args the VM arguments.
* the VM arguments.
*/ */
public static void main(final String[] args) { public static void main(final String[] args) {
SpringApplication.run(DDIStart.class, args); SpringApplication.run(DDIStart.class, args);

View File

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

View File

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

View File

@@ -9,6 +9,9 @@
*/ */
package org.eclipse.hawkbit.app.ddi; 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.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
import io.qameta.allure.Story; import io.qameta.allure.Story;
@@ -18,17 +21,14 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.test.context.TestPropertySource; 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") @Feature("Integration Test - Security")
@Story("PreAuthorized enabled") @Story("PreAuthorized enabled")
@TestPropertySource(properties = {"spring.flyway.enabled=true"}) @TestPropertySource(properties = { "spring.flyway.enabled=true" })
public class PreAuthorizeEnabledTest extends AbstractSecurityTest { public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test @Test
@Description("Tests whether request fail if a role is forbidden for the user") @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 { public void failIfNoRole() throws Exception {
mvc.perform(get("/DEFAULT/controller/v1/controllerId")).andExpect(result -> 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()));

View File

@@ -1,5 +1,7 @@
# hawkBit DMF Server (EXPERIMENTAL!) # 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 ## On your own workstation
@@ -18,10 +20,17 @@ run org.eclipse.hawkbit.app.dmf.DMFStart
``` ```
### Clustering (Experimental!!!) ### 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 ## 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! **Note**: If Protostuff is enabled it shall be enabled on all microservices!

View File

@@ -9,7 +9,7 @@
SPDX-License-Identifier: EPL-2.0 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"> 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>

View File

@@ -1,5 +1,7 @@
# hawkBit Management Server (EXPERIMENTAL!) # 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 ## On your own workstation
@@ -18,14 +20,22 @@ run org.eclipse.hawkbit.app.mgmt.MgmtServerStart
``` ```
### Usage ### Usage
The Management API can be accessed via http://localhost:8080/rest/v1 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 The root url http://localhost:8080 will redirect directly to the Swagger Management UI
### Clustering (Experimental!!!) ### 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 ## 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! **Note**: If Protostuff is enabled it shall be enabled on all microservices!

View File

@@ -9,7 +9,7 @@
SPDX-License-Identifier: EPL-2.0 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"> 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>

View File

@@ -37,10 +37,8 @@ public class ErrorController extends BasicErrorController {
/** /**
* A new {@link ErrorController}. * A new {@link ErrorController}.
* *
* @param errorAttributes * @param errorAttributes the error attributes
* the error attributes * @param serverProperties configuration properties
* @param serverProperties
* configuration properties
*/ */
public ErrorController(final ErrorAttributes errorAttributes, final ServerProperties serverProperties) { public ErrorController(final ErrorAttributes errorAttributes, final ServerProperties serverProperties) {
super(errorAttributes, serverProperties.getError()); 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. * A {@link SpringBootApplication} annotated class with a main method to start.
* The minimal configuration for the stand alone hawkBit server. * The minimal configuration for the stand alone hawkBit server.
*
*/ */
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit") @SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
@EnableHawkbitManagedSecurityConfiguration @EnableHawkbitManagedSecurityConfiguration

View File

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

View File

@@ -9,17 +9,14 @@
*/ */
package org.eclipse.hawkbit.app.mgmt; 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.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import io.qameta.allure.Description; 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.Feature;
import io.qameta.allure.Story; import io.qameta.allure.Story;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = { "hawkbit.server.security.allowedHostNames=localhost", @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.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 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.im.authentication.SpRole;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
@SpringBootTest( @SpringBootTest(
properties = { properties = {
"hawkbit.dmf.rabbitmq.enabled=false", "hawkbit.dmf.rabbitmq.enabled=false",
@@ -35,7 +33,7 @@ import io.qameta.allure.Story;
CorsTest.ALLOWED_ORIGIN_FIRST + "," + CorsTest.ALLOWED_ORIGIN_FIRST + "," +
CorsTest.ALLOWED_ORIGIN_SECOND, CorsTest.ALLOWED_ORIGIN_SECOND,
"hawkbit.server.security.cors.exposedHeaders=" + "hawkbit.server.security.cors.exposedHeaders=" +
HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN}) HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN })
@Feature("Integration Test - Security") @Feature("Integration Test - Security")
@Story("CORS") @Story("CORS")
public class CorsTest extends AbstractSecurityTest { public class CorsTest extends AbstractSecurityTest {

View File

@@ -9,6 +9,11 @@
*/ */
package org.eclipse.hawkbit.app.mgmt; 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 com.fasterxml.jackson.databind.ObjectMapper;
import io.qameta.allure.Description; import io.qameta.allure.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
@@ -19,18 +24,13 @@ import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus; 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") @Feature("Integration Test - Security")
@Story("PreAuthorized enabled") @Story("PreAuthorized enabled")
public class PreAuthorizeEnabledTest extends AbstractSecurityTest { public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test @Test
@Description("Tests whether request fail if a role is forbidden for the user") @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 { public void failIfNoRole() throws Exception {
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> mvc.perform(get("/rest/v1/distributionsets")).andExpect(result ->
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value())); assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()));
@@ -57,7 +57,7 @@ public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test @Test
@Description("Tests whether read tenant config request fail if a tenant config (or read read) is not " + @Description("Tests whether read tenant config request fail if a tenant config (or read read) is not " +
"granted for the user") "granted for the user")
@WithUser(authorities = { SpPermission.READ_TARGET } ) @WithUser(authorities = { SpPermission.READ_TARGET })
public void onlyDSIfNoTenantConfig() throws Exception { public void onlyDSIfNoTenantConfig() throws Exception {
mvc.perform(get("/rest/v1/system/configs")).andExpect(result -> { mvc.perform(get("/rest/v1/system/configs")).andExpect(result -> {
// returns default DS type because of READ_TARGET // returns default DS type because of READ_TARGET

View File

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

View File

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

View File

@@ -9,13 +9,8 @@
*/ */
package org.eclipse.hawkbit.ui.simple; package org.eclipse.hawkbit.ui.simple;
import org.eclipse.hawkbit.ui.simple.view.TargetView; import java.util.Optional;
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 com.vaadin.flow.component.Unit; import com.vaadin.flow.component.Unit;
import com.vaadin.flow.component.applayout.AppLayout; import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle; 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.router.PageTitle;
import com.vaadin.flow.server.auth.AccessAnnotationChecker; import com.vaadin.flow.server.auth.AccessAnnotationChecker;
import com.vaadin.flow.theme.lumo.LumoUtility; import com.vaadin.flow.theme.lumo.LumoUtility;
import org.eclipse.hawkbit.ui.simple.security.AuthenticatedUser;
import java.util.Optional; 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. * The main view is a top-level placeholder for other views.
*/ */
public class MainLayout extends AppLayout { public class MainLayout extends AppLayout {
private H2 viewTitle;
private final transient AuthenticatedUser authenticatedUser; private final transient AuthenticatedUser authenticatedUser;
private final AccessAnnotationChecker accessChecker; private final AccessAnnotationChecker accessChecker;
private H2 viewTitle;
public MainLayout(final AuthenticatedUser authenticatedUser, final AccessAnnotationChecker accessChecker) { public MainLayout(final AuthenticatedUser authenticatedUser, final AccessAnnotationChecker accessChecker) {
this.authenticatedUser = authenticatedUser; this.authenticatedUser = authenticatedUser;
@@ -62,6 +61,15 @@ public class MainLayout extends AppLayout {
addHeaderContent(); addHeaderContent();
} }
@Override
protected void afterNavigation() {
super.afterNavigation();
viewTitle.setText(
Optional.ofNullable(getContent().getClass().getAnnotation(PageTitle.class))
.map(PageTitle::value)
.orElse(""));
}
private void addHeaderContent() { private void addHeaderContent() {
final DrawerToggle toggle = new DrawerToggle(); final DrawerToggle toggle = new DrawerToggle();
toggle.setAriaLabel("Menu toggle"); toggle.setAriaLabel("Menu toggle");
@@ -143,13 +151,4 @@ public class MainLayout extends AppLayout {
return layout; 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; 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.component.page.AppShellConfigurator;
import com.vaadin.flow.server.PWA; import com.vaadin.flow.server.PWA;
import com.vaadin.flow.theme.Theme; 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.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; 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) @Theme(themeClass = Lumo.class)
@PWA(name="hawkBit UI", shortName="hawkBit UI") @PWA(name = "hawkBit UI", shortName = "hawkBit UI")
@SpringBootApplication @SpringBootApplication
@Import(FeignClientsConfiguration.class) @Import(FeignClientsConfiguration.class)
public class SimpleUIApp implements AppShellConfigurator { public class SimpleUIApp implements AppShellConfigurator {
@@ -86,7 +86,7 @@ public class SimpleUIApp implements AppShellConfigurator {
// accepts all user / pass, just delegating them to the feign client // accepts all user / pass, just delegating them to the feign client
@Bean @Bean
AuthenticationManager authenticationManager(final HawkbitMgmtClient hawkbitClient) { AuthenticationManager authenticationManager(final HawkbitMgmtClient hawkbitClient) {
return authentication-> { return authentication -> {
final String username = authentication.getName(); final String username = authentication.getName();
final String password = authentication.getCredentials().toString(); final String password = authentication.getCredentials().toString();
@@ -119,6 +119,7 @@ public class SimpleUIApp implements AppShellConfigurator {
return new UsernamePasswordAuthenticationToken( return new UsernamePasswordAuthenticationToken(
username, password, username, password,
roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).toList()) { roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).toList()) {
@Override @Override
public void eraseCredentials() { public void eraseCredentials() {
// don't erase credentials because they will be used // don't erase credentials because they will be used

View File

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

View File

@@ -9,8 +9,8 @@
*/ */
package org.eclipse.hawkbit.ui.simple.security; package org.eclipse.hawkbit.ui.simple.security;
import org.eclipse.hawkbit.ui.simple.view.LoginView;
import com.vaadin.flow.spring.security.VaadinWebSecurity; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;

View File

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

View File

@@ -12,21 +12,21 @@ package org.eclipse.hawkbit.ui.simple.view;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import lombok.extern.slf4j.Slf4j; import jakarta.annotation.security.RolesAllowed;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.simple.MainLayout;
import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationValueRequest;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Key; import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.checkbox.Checkbox; 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.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.textfield.NumberField; 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.PageTitle;
import com.vaadin.flow.router.Route; 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") @PageTitle("Config")
@Route(value = "config", layout = MainLayout.class) @Route(value = "config", layout = MainLayout.class)
@@ -75,7 +75,8 @@ public class ConfigView extends VerticalLayout {
nf.getElement().getStyle().set(WIDTH, PX_300); nf.getElement().getStyle().set(WIDTH, PX_300);
value = nf; value = nf;
} else { } else {
log.debug("Unexpected value type: {} -> {} (class: {})", k, v.getValue(), v.getValue() == null ? "null" : v.getValue().getClass()); log.debug("Unexpected value type: {} -> {} (class: {})", k, v.getValue(),
v.getValue() == null ? "null" : v.getValue().getClass());
} }
if (value != null) { if (value != null) {
add(value); add(value);

View File

@@ -9,12 +9,17 @@
*/ */
package org.eclipse.hawkbit.ui.simple.view; package org.eclipse.hawkbit.ui.simple.view;
import org.eclipse.hawkbit.ui.simple.MainLayout; import java.util.Collections;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient; import java.util.Date;
import org.eclipse.hawkbit.ui.simple.view.util.Filter; import java.util.HashSet;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid; import java.util.List;
import org.eclipse.hawkbit.ui.simple.view.util.TableView; import java.util.Map;
import org.eclipse.hawkbit.ui.simple.view.util.Utils; 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.Component;
import com.vaadin.flow.component.Key; import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button; 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.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; 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.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPost; 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.distributionsettype.MgmtDistributionSetType;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule; 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.softwaremodule.MgmtSoftwareModuleAssigment;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag; import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient;
import java.util.Collections; import org.eclipse.hawkbit.ui.simple.MainLayout;
import java.util.Date; import org.eclipse.hawkbit.ui.simple.view.util.Filter;
import java.util.HashSet; import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid;
import java.util.List; import org.eclipse.hawkbit.ui.simple.view.util.TableView;
import java.util.Map; import org.eclipse.hawkbit.ui.simple.view.util.Utils;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
@PageTitle("Distribution Sets") @PageTitle("Distribution Sets")
@Route(value = "distribution_sets", layout = MainLayout.class) @Route(value = "distribution_sets", layout = MainLayout.class)
@RolesAllowed({"DISTRIBUTION_SET_READ"}) @RolesAllowed({ "DISTRIBUTION_SET_READ" })
@Uses(Icon.class) @Uses(Icon.class)
public class DistributionSetView extends TableView<MgmtDistributionSet, Long> { public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
@@ -93,6 +94,7 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
return new SelectionGrid<>( return new SelectionGrid<>(
new SelectionGrid.EntityRepresentation<>( new SelectionGrid.EntityRepresentation<>(
MgmtSoftwareModule.class, MgmtSoftwareModule::getModuleId) { MgmtSoftwareModule.class, MgmtSoftwareModule::getModuleId) {
@Override @Override
protected void addColumns(Grid<MgmtSoftwareModule> grid) { protected void addColumns(Grid<MgmtSoftwareModule> grid) {
grid.addColumn(MgmtSoftwareModule::getModuleId).setHeader(Constants.ID).setAutoWidth(true); grid.addColumn(MgmtSoftwareModule::getModuleId).setHeader(Constants.ID).setAutoWidth(true);
@@ -258,7 +260,7 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
close(); close();
final long distributionSetId = hawkbitClient.getDistributionSetRestApi() final long distributionSetId = hawkbitClient.getDistributionSetRestApi()
.createDistributionSets( .createDistributionSets(
List.of((MgmtDistributionSetRequestBodyPost)new MgmtDistributionSetRequestBodyPost() List.of((MgmtDistributionSetRequestBodyPost) new MgmtDistributionSetRequestBodyPost()
.setType(type.getValue().getKey()) .setType(type.getValue().getKey())
.setName(name.getValue()) .setName(name.getValue())
.setVersion(version.getValue()) .setVersion(version.getValue())

View File

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

View File

@@ -9,17 +9,20 @@
*/ */
package org.eclipse.hawkbit.ui.simple.view; package org.eclipse.hawkbit.ui.simple.view;
import com.vaadin.flow.component.checkbox.Checkbox; import java.time.ZoneOffset;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient; import java.util.Date;
import org.eclipse.hawkbit.ui.simple.MainLayout; import java.util.List;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid; import java.util.Map;
import org.eclipse.hawkbit.ui.simple.view.util.TableView; import java.util.concurrent.CompletableFuture;
import org.eclipse.hawkbit.ui.simple.view.util.Utils; import java.util.stream.Stream;
import org.eclipse.hawkbit.ui.simple.view.util.Filter;
import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.Component; import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Key; import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.Text; import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.button.Button; 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.datetimepicker.DateTimePicker;
import com.vaadin.flow.component.dependency.Uses; import com.vaadin.flow.component.dependency.Uses;
import com.vaadin.flow.component.formlayout.FormLayout; 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.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; 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.MgmtActionType;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutCondition; 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.rollout.MgmtRolloutRestRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroupResponseBody; import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroupResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; 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 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") @PageTitle("Rollouts")
@Route(value = "rollouts", layout = MainLayout.class) @Route(value = "rollouts", layout = MainLayout.class)
@RolesAllowed({"ROLLOUT_READ"}) @RolesAllowed({ "ROLLOUT_READ" })
@Uses(Icon.class) @Uses(Icon.class)
public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> { public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
@@ -103,6 +104,7 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
private static SelectionGrid<MgmtRolloutGroupResponseBody, Long> createGroupGrid() { private static SelectionGrid<MgmtRolloutGroupResponseBody, Long> createGroupGrid() {
return new SelectionGrid<>( return new SelectionGrid<>(
new SelectionGrid.EntityRepresentation<>(MgmtRolloutGroupResponseBody.class, MgmtRolloutGroupResponseBody::getRolloutGroupId) { new SelectionGrid.EntityRepresentation<>(MgmtRolloutGroupResponseBody.class, MgmtRolloutGroupResponseBody::getRolloutGroupId) {
@Override @Override
protected void addColumns(final Grid<MgmtRolloutGroupResponseBody> grid) { protected void addColumns(final Grid<MgmtRolloutGroupResponseBody> grid) {
grid.addColumn(MgmtRolloutGroupResponseBody::getRolloutGroupId).setHeader(Constants.ID).setAutoWidth(true); 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 Grid<MgmtRolloutResponseBody> grid;
private final transient HawkbitMgmtClient hawkbitClient; 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.rolloutId = rollout.getRolloutId();
this.grid = grid; this.grid = grid;
this.hawkbitClient = hawkbitClient; this.hawkbitClient = hawkbitClient;
@@ -258,10 +261,6 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
private static class CreateDialog extends Utils.BaseDialog<Void> { private static class CreateDialog extends Utils.BaseDialog<Void> {
private enum StartType {
MANUAL, AUTO, SCHEDULED
}
private final TextField name; private final TextField name;
private final Select<MgmtDistributionSet> distributionSet; private final Select<MgmtDistributionSet> distributionSet;
private final Select<MgmtTargetFilterQuery> targetFilter; private final Select<MgmtTargetFilterQuery> targetFilter;
@@ -274,7 +273,6 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
private final NumberField triggerThreshold; private final NumberField triggerThreshold;
private final NumberField errorThreshold; private final NumberField errorThreshold;
private final Checkbox dynamic = new Checkbox(Constants.DYNAMIC); private final Checkbox dynamic = new Checkbox(Constants.DYNAMIC);
private final Button create = new Button("Create"); private final Button create = new Button("Create");
private CreateDialog(final HawkbitMgmtClient hawkbitClient) { private CreateDialog(final HawkbitMgmtClient hawkbitClient) {
@@ -443,5 +441,9 @@ public class RolloutView extends TableView<MgmtRolloutResponseBody, Long> {
hawkbitClient.getRolloutRestApi().create(request).getBody(); hawkbitClient.getRolloutRestApi().create(request).getBody();
}); });
} }
private enum StartType {
MANUAL, AUTO, SCHEDULED
}
} }
} }

View File

@@ -9,12 +9,20 @@
*/ */
package org.eclipse.hawkbit.ui.simple.view; package org.eclipse.hawkbit.ui.simple.view;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient; import java.io.File;
import org.eclipse.hawkbit.ui.simple.MainLayout; import java.io.IOException;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid; import java.io.InputStream;
import org.eclipse.hawkbit.ui.simple.view.util.TableView; import java.util.Collections;
import org.eclipse.hawkbit.ui.simple.view.util.Utils; import java.util.Date;
import org.eclipse.hawkbit.ui.simple.view.util.Filter; 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.Component;
import com.vaadin.flow.component.Key; import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button; 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.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; 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.artifact.MgmtArtifact;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule; 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.softwaremodule.MgmtSoftwareModuleRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType; 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.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile; 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") @PageTitle("Software Modules")
@Route(value = "software_modules", layout = MainLayout.class) @Route(value = "software_modules", layout = MainLayout.class)
@RolesAllowed({"SOFTWARE_MODULE_READ"}) @RolesAllowed({ "SOFTWARE_MODULE_READ" })
@Uses(Icon.class) @Uses(Icon.class)
public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> { 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) { new SelectionGrid.EntityRepresentation<>(MgmtSoftwareModule.class, MgmtSoftwareModule::getModuleId) {
private final SoftwareModuleDetails details = new SoftwareModuleDetails(hawkbitClient); private final SoftwareModuleDetails details = new SoftwareModuleDetails(hawkbitClient);
@Override @Override
protected void addColumns(final Grid<MgmtSoftwareModule> grid) { protected void addColumns(final Grid<MgmtSoftwareModule> grid) {
grid.addColumn(MgmtSoftwareModule::getModuleId).setHeader(Constants.ID).setAutoWidth(true); 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() { private static SelectionGrid<MgmtArtifact, Long> createArtifactGrid() {
return new SelectionGrid<>( return new SelectionGrid<>(
new SelectionGrid.EntityRepresentation<>(MgmtArtifact.class, MgmtArtifact::getArtifactId) { new SelectionGrid.EntityRepresentation<>(MgmtArtifact.class, MgmtArtifact::getArtifactId) {
@Override @Override
protected void addColumns(final Grid<MgmtArtifact> grid) { protected void addColumns(final Grid<MgmtArtifact> grid) {
grid.addColumn(MgmtArtifact::getArtifactId).setHeader(Constants.ID).setAutoWidth(true); grid.addColumn(MgmtArtifact::getArtifactId).setHeader(Constants.ID).setAutoWidth(true);
@@ -329,7 +331,7 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
public MultipartFileImpl(final FileBuffer fileBuffer, final long contentLength, final String mimeType) { public MultipartFileImpl(final FileBuffer fileBuffer, final long contentLength, final String mimeType) {
this.fileBuffer = fileBuffer; this.fileBuffer = fileBuffer;
this. contentLength = contentLength; this.contentLength = contentLength;
this.mimeType = mimeType; this.mimeType = mimeType;
} }

View File

@@ -9,12 +9,17 @@
*/ */
package org.eclipse.hawkbit.ui.simple.view; package org.eclipse.hawkbit.ui.simple.view;
import org.eclipse.hawkbit.ui.simple.HawkbitMgmtClient; import java.util.Collections;
import org.eclipse.hawkbit.ui.simple.view.util.Filter; import java.util.Date;
import org.eclipse.hawkbit.ui.simple.MainLayout; import java.util.LinkedList;
import org.eclipse.hawkbit.ui.simple.view.util.SelectionGrid; import java.util.List;
import org.eclipse.hawkbit.ui.simple.view.util.TableView; import java.util.Map;
import org.eclipse.hawkbit.ui.simple.view.util.Utils; 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.Component;
import com.vaadin.flow.component.Key; import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button; 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.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; 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.tag.MgmtTag;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; 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.target.MgmtTargetRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; 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.targetfilter.MgmtTargetFilterQueryRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.targettype.MgmtTargetType; 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 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") @PageTitle("Targets")
@Route(value = "targets", layout = MainLayout.class) @Route(value = "targets", layout = MainLayout.class)
@RolesAllowed({"TARGET_READ"}) @RolesAllowed({ "TARGET_READ" })
@Uses(Icon.class) @Uses(Icon.class)
public class TargetView extends TableView<MgmtTarget, String> { public class TargetView extends TableView<MgmtTarget, String> {
@@ -156,7 +157,8 @@ public class TargetView extends TableView<MgmtTarget, String> {
.getFilters(0, 30, null, null, null) .getFilters(0, 30, null, null, null)
.getBody().getContent()) .getBody().getContent())
.orElse(Collections.emptyList())); .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(); savedFilters.setWidthFull();
textFilter.setWidthFull(); textFilter.setWidthFull();

View File

@@ -9,6 +9,15 @@
*/ */
package org.eclipse.hawkbit.ui.simple.view.util; 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.Component;
import com.vaadin.flow.component.HasValue; import com.vaadin.flow.component.HasValue;
import com.vaadin.flow.component.button.Button; 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.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.theme.lumo.LumoUtility; 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 { public class Filter extends Div {
private transient Rsql rsql; private transient Rsql rsql;
@@ -46,7 +46,7 @@ public class Filter extends Div {
filtersDiv.add(primaryRsql.components()); filtersDiv.add(primaryRsql.components());
filtersDiv.addClassName(LumoUtility.Gap.SMALL); 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.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
searchBtn.addClickListener(e -> changeListener.accept(rsql.filter())); searchBtn.addClickListener(e -> changeListener.accept(rsql.filter()));
final Button resetBtn = Utils.tooltip(new Button(VaadinIcon.ERASER.create()), "Reset"); 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; 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.HashSet;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@@ -21,12 +16,16 @@ import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; 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 // 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; private volatile String rsqlFilter;
public SelectionGrid( public SelectionGrid(
final EntityRepresentation<T, ID> entityRepresentation) { final EntityRepresentation<T, ID> entityRepresentation) {
this(entityRepresentation, null); this(entityRepresentation, null);

View File

@@ -9,16 +9,16 @@
*/ */
package org.eclipse.hawkbit.ui.simple.view.util; 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.concurrent.CompletionStage;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; 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 { public class TableView<T, ID> extends Div implements Constants {
protected SelectionGrid<T, ID> selectionGrid; 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) { final BiFunction<Query<T, Void>, String, Stream<T>> queryFn) {
this(rsql, null, entityRepresentation, queryFn, null, null); this(rsql, null, entityRepresentation, queryFn, null, null);
} }
public TableView( public TableView(
final Filter.Rsql rsql, final Filter.Rsql alternativeRsql, final Filter.Rsql rsql, final Filter.Rsql alternativeRsql,
final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation, final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation,
final BiFunction<Query<T, Void>, String, Stream<T>> queryFn) { 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( public TableView(
final Filter.Rsql rsql, final Filter.Rsql rsql,
final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation, 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) { final Function<SelectionGrid<T, ID>, CompletionStage<Void>> removeHandler) {
this(rsql, null, entityRepresentation, queryFn, addHandler, removeHandler); this(rsql, null, entityRepresentation, queryFn, addHandler, removeHandler);
} }
public TableView( public TableView(
final Filter.Rsql rsql, final Filter.Rsql alternativeRsql, final Filter.Rsql rsql, final Filter.Rsql alternativeRsql,
final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation, final SelectionGrid.EntityRepresentation<T, ID> entityRepresentation,

View File

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

View File

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

View File

@@ -41,7 +41,8 @@ Add to your `pom.xml` :
</dependency> </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` : Add to your `application.properties` :

View File

@@ -9,7 +9,7 @@
SPDX-License-Identifier: EPL-2.0 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"> 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>

View File

@@ -37,10 +37,8 @@ public class ErrorController extends BasicErrorController {
/** /**
* A new {@link ErrorController}. * A new {@link ErrorController}.
* *
* @param errorAttributes * @param errorAttributes the error attributes
* the error attributes * @param serverProperties configuration properties
* @param serverProperties
* configuration properties
*/ */
public ErrorController(final ErrorAttributes errorAttributes, final ServerProperties serverProperties) { public ErrorController(final ErrorAttributes errorAttributes, final ServerProperties serverProperties) {
super(errorAttributes, serverProperties.getError()); 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. * A {@link SpringBootApplication} annotated class with a main method to start.
* The minimal configuration for the stand alone hawkBit server. * The minimal configuration for the stand alone hawkBit server.
*
*/ */
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit") @SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
@EnableHawkbitManagedSecurityConfiguration @EnableHawkbitManagedSecurityConfiguration

View File

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

View File

@@ -9,17 +9,14 @@
*/ */
package org.eclipse.hawkbit.app; 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.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import io.qameta.allure.Description; 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.Feature;
import io.qameta.allure.Story; import io.qameta.allure.Story;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = { "hawkbit.server.security.allowedHostNames=localhost", @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.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 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.im.authentication.SpRole;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
@SpringBootTest( @SpringBootTest(
properties = { properties = {
"hawkbit.dmf.rabbitmq.enabled=false", "hawkbit.dmf.rabbitmq.enabled=false",
@@ -35,7 +33,7 @@ import io.qameta.allure.Story;
CorsTest.ALLOWED_ORIGIN_FIRST + "," + CorsTest.ALLOWED_ORIGIN_FIRST + "," +
CorsTest.ALLOWED_ORIGIN_SECOND, CorsTest.ALLOWED_ORIGIN_SECOND,
"hawkbit.server.security.cors.exposedHeaders=" + "hawkbit.server.security.cors.exposedHeaders=" +
HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN}) HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN })
@Feature("Integration Test - Security") @Feature("Integration Test - Security")
@Story("CORS") @Story("CORS")
public class CorsTest extends AbstractSecurityTest { public class CorsTest extends AbstractSecurityTest {

View File

@@ -9,6 +9,11 @@
*/ */
package org.eclipse.hawkbit.app; 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 com.fasterxml.jackson.databind.ObjectMapper;
import io.qameta.allure.Description; import io.qameta.allure.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
@@ -19,18 +24,13 @@ import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus; 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") @Feature("Integration Test - Security")
@Story("PreAuthorized enabled") @Story("PreAuthorized enabled")
public class PreAuthorizeEnabledTest extends AbstractSecurityTest { public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test @Test
@Description("Tests whether request fail if a role is forbidden for the user") @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 { public void failIfNoRole() throws Exception {
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> mvc.perform(get("/rest/v1/distributionsets")).andExpect(result ->
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value())); assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()));
@@ -57,7 +57,7 @@ public class PreAuthorizeEnabledTest extends AbstractSecurityTest {
@Test @Test
@Description("Tests whether read tenant config request fail if a tenant config (or read read) is not " + @Description("Tests whether read tenant config request fail if a tenant config (or read read) is not " +
"granted for the user") "granted for the user")
@WithUser(authorities = { SpPermission.READ_TARGET } ) @WithUser(authorities = { SpPermission.READ_TARGET })
public void onlyDSIfNoTenantConfig() throws Exception { public void onlyDSIfNoTenantConfig() throws Exception {
mvc.perform(get("/rest/v1/system/configs")).andExpect(result -> { mvc.perform(get("/rest/v1/system/configs")).andExpect(result -> {
// returns default DS type because of READ_TARGET // returns default DS type because of READ_TARGET

View File

@@ -9,7 +9,8 @@
SPDX-License-Identifier: EPL-2.0 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>