Merge remote-tracking branch 'origin/master' into fix_migration_to_new_spring_boot_version_merge

Signed-off-by: Ammar Bikic <ammar.bikic@bosch.io>
This commit is contained in:
Ammar Bikic
2020-10-23 13:30:59 +02:00
33 changed files with 206 additions and 107 deletions

View File

@@ -15,10 +15,16 @@ jobs:
docker: docker:
- image: circleci/openjdk:8u181-jdk-stretch - image: circleci/openjdk:8u181-jdk-stretch
auth:
username: $DOCKERHUB_USER
password: $DOCKERHUB_ACCESSTOKEN
environment: environment:
_JAVA_OPTIONS: "-Xms512m -Xmx1024m" _JAVA_OPTIONS: "-Xms512m -Xmx1024m"
- image: rabbitmq:3-management - image: rabbitmq:3-management
auth:
username: $DOCKERHUB_USER
password: $DOCKERHUB_ACCESSTOKEN
environment: environment:
RABBITMQ_DEFAULT_VHOST: "/" RABBITMQ_DEFAULT_VHOST: "/"

View File

@@ -20,6 +20,7 @@ import org.eclipse.hawkbit.ui.push.HawkbitEventPermissionChecker;
import org.eclipse.hawkbit.ui.push.HawkbitEventProvider; import org.eclipse.hawkbit.ui.push.HawkbitEventProvider;
import org.eclipse.hawkbit.ui.push.UIEventPermissionChecker; import org.eclipse.hawkbit.ui.push.UIEventPermissionChecker;
import org.eclipse.hawkbit.ui.push.UIEventProvider; import org.eclipse.hawkbit.ui.push.UIEventProvider;
import org.eclipse.hawkbit.ui.utils.SpringContextHolder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
@@ -81,6 +82,16 @@ public class MgmtUiAutoConfiguration {
return new HawkbitEventPermissionChecker(permChecker); return new HawkbitEventPermissionChecker(permChecker);
} }
/**
* Bean for creating a singleton instance of the {@link SpringContextHolder}
*
* @return the singleton instance of the {@link SpringContextHolder}
*/
@Bean
SpringContextHolder springContextHolder() {
return SpringContextHolder.getInstance();
}
/** /**
* The UI scoped event push strategy. Session scope is necessary, that every * The UI scoped event push strategy. Session scope is necessary, that every
* UI has an own strategy. * UI has an own strategy.

View File

@@ -35,7 +35,7 @@ public final class EventPublisherHolder {
} }
/** /**
* @return the cache manager holder singleton instance * @return the event publisher holder singleton instance
*/ */
public static EventPublisherHolder getInstance() { public static EventPublisherHolder getInstance() {
return SINGLETON; return SINGLETON;

View File

@@ -23,6 +23,7 @@ import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository; import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
/** /**
@@ -102,11 +103,12 @@ public class NoCountPagingRepository {
} }
@Override @Override
protected Page<T> readPage(final TypedQuery<T> query, final Pageable pageable, final Specification<T> spec) { protected <S extends T> Page<S> readPage(final TypedQuery<S> query, final Class<S> domainClass,
final Pageable pageable, @Nullable final Specification<S> spec) {
query.setFirstResult((int) pageable.getOffset()); query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize()); query.setMaxResults(pageable.getPageSize());
final List<T> content = query.getResultList(); final List<S> content = query.getResultList();
return new PageImpl<>(content, pageable, content.size()); return new PageImpl<>(content, pageable, content.size());
} }

View File

@@ -78,6 +78,7 @@ import org.slf4j.LoggerFactory;
@Index(name = "sp_idx_target_01", columnList = "tenant,name,assigned_distribution_set"), @Index(name = "sp_idx_target_01", columnList = "tenant,name,assigned_distribution_set"),
@Index(name = "sp_idx_target_03", columnList = "tenant,controller_id,assigned_distribution_set"), @Index(name = "sp_idx_target_03", columnList = "tenant,controller_id,assigned_distribution_set"),
@Index(name = "sp_idx_target_04", columnList = "tenant,created_at"), @Index(name = "sp_idx_target_04", columnList = "tenant,created_at"),
@Index(name = "sp_idx_target_05", columnList = "tenant,last_modified_at"),
@Index(name = "sp_idx_target_prim", columnList = "tenant,id") }, uniqueConstraints = @UniqueConstraint(columnNames = { @Index(name = "sp_idx_target_prim", columnList = "tenant,id") }, uniqueConstraints = @UniqueConstraint(columnNames = {
"controller_id", "tenant" }, name = "uk_tenant_controller_id")) "controller_id", "tenant" }, name = "uk_tenant_controller_id"))
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for

View File

@@ -0,0 +1 @@
CREATE INDEX sp_idx_target_05 ON sp_target (tenant, last_modified_at);

View File

@@ -0,0 +1 @@
CREATE INDEX sp_idx_target_05 ON sp_target (tenant, last_modified_at);

View File

@@ -0,0 +1 @@
CREATE INDEX sp_idx_target_05 ON sp_target (tenant, last_modified_at);

View File

@@ -0,0 +1,3 @@
CREATE INDEX sp_idx_target_05
ON sp_target
USING BTREE (tenant, last_modified_at);

View File

@@ -0,0 +1 @@
CREATE INDEX sp_idx_target_05 ON sp_target (tenant, last_modified_at);

View File

@@ -18,11 +18,12 @@ import org.eclipse.hawkbit.ui.push.UIEventProvider;
import org.eclipse.hawkbit.ui.themes.HawkbitTheme; import org.eclipse.hawkbit.ui.themes.HawkbitTheme;
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil;
import org.eclipse.hawkbit.ui.utils.SpringContextHelper;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.support.AbstractApplicationContext;
import org.vaadin.spring.events.EventBus.UIEventBus; import org.vaadin.spring.events.EventBus.UIEventBus;
import com.vaadin.annotations.Theme; import com.vaadin.annotations.Theme;
@@ -100,6 +101,19 @@ public abstract class AbstractHawkbitUI extends UI implements DetachListener {
if (pushStrategy != null) { if (pushStrategy != null) {
pushStrategy.clean(); pushStrategy.clean();
clearContextListener();
}
}
private void clearContextListener() {
if (pushStrategy instanceof ApplicationListener && context instanceof AbstractApplicationContext) {
final ApplicationListener<?> listener = (ApplicationListener<?>) pushStrategy;
((AbstractApplicationContext) context).getApplicationListeners().remove(listener);
// we do not need to explicitly remove the listener from
// ApplicationEventMulticaster because it is done by
// UIBeanStore#destroy delegating to
// ApplicationListenerDetector#postProcessBeforeDestruction
} }
} }
@@ -110,7 +124,6 @@ public abstract class AbstractHawkbitUI extends UI implements DetachListener {
pushStrategy.init(getUI()); pushStrategy.init(getUI());
} }
addDetachListener(this); addDetachListener(this);
SpringContextHelper.setContext(context);
Responsive.makeResponsive(this); Responsive.makeResponsive(this);
addStyleName(ValoTheme.UI_WITH_MENU); addStyleName(ValoTheme.UI_WITH_MENU);

View File

@@ -8,11 +8,13 @@
*/ */
package org.eclipse.hawkbit.ui.artifacts.smtable; package org.eclipse.hawkbit.ui.artifacts.smtable;
import java.util.function.BooleanSupplier;
import org.eclipse.hawkbit.ui.common.detailslayout.MetaDataAddUpdateWindowLayout; import org.eclipse.hawkbit.ui.common.detailslayout.MetaDataAddUpdateWindowLayout;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import com.vaadin.ui.CheckBox; import com.vaadin.ui.CheckBox;
import com.vaadin.ui.ComponentContainer; import com.vaadin.ui.VerticalLayout;
/** /**
* Class for sm metadata add/update window layout. * Class for sm metadata add/update window layout.
@@ -24,23 +26,28 @@ public class SmMetaDataAddUpdateWindowLayout extends MetaDataAddUpdateWindowLayo
* Constructor for AbstractTagWindowLayout * Constructor for AbstractTagWindowLayout
* *
* @param i18n * @param i18n
* I18N * VaadinMessageSource
* @param hasMetadataChangePermission
* checks the permission allowing to change metadata entities
*/ */
public SmMetaDataAddUpdateWindowLayout(final VaadinMessageSource i18n) { public SmMetaDataAddUpdateWindowLayout(final VaadinMessageSource i18n,
super(i18n); final BooleanSupplier hasMetadataChangePermission) {
super(i18n, hasMetadataChangePermission);
this.isVisibleForTarget = metaDataComponentBuilder.createVisibleForTargetsField(binder); this.isVisibleForTarget = metaDataComponentBuilder.createVisibleForTargetsField(binder);
} }
/**
* @return form layout checkbox container for software module
*/
@Override @Override
public ComponentContainer getRootComponent() { protected VerticalLayout getMetadataAddUpdateLayout() {
final ComponentContainer formLayout = super.getRootComponent(); final VerticalLayout addUpdateLayout = super.getMetadataAddUpdateLayout();
addUpdateLayout.addComponent(isVisibleForTarget);
formLayout.addComponent(isVisibleForTarget); return addUpdateLayout;
}
return formLayout; @Override
protected void disableMetadataInputComponents() {
super.disableMetadataInputComponents();
isVisibleForTarget.setEnabled(false);
} }
} }

View File

@@ -67,9 +67,10 @@ public class SmMetaDataWindowLayout extends AbstractMetaDataWindowLayout<Long> {
this.entityFactory = entityFactory; this.entityFactory = entityFactory;
this.smMetaDataWindowGrid = new MetaDataWindowGrid<>(i18n, eventBus, permChecker, uiNotification, this.smMetaDataWindowGrid = new MetaDataWindowGrid<>(i18n, eventBus, permChecker, uiNotification,
new SmMetaDataDataProvider(smManagement), this::deleteMetaData); new SmMetaDataDataProvider(smManagement), this::hasMetadataChangePermission, this::deleteMetaData);
this.smMetaDataAddUpdateWindowLayout = new SmMetaDataAddUpdateWindowLayout(i18n); this.smMetaDataAddUpdateWindowLayout = new SmMetaDataAddUpdateWindowLayout(i18n,
this::hasMetadataChangePermission);
this.addSmMetaDataWindowController = new AddMetaDataWindowController(i18n, uiNotification, this.addSmMetaDataWindowController = new AddMetaDataWindowController(i18n, uiNotification,
smMetaDataAddUpdateWindowLayout, this::createMetaData, this::isDuplicate); smMetaDataAddUpdateWindowLayout, this::createMetaData, this::isDuplicate);
this.updateSmMetaDataWindowController = new UpdateMetaDataWindowController(i18n, uiNotification, this.updateSmMetaDataWindowController = new UpdateMetaDataWindowController(i18n, uiNotification,

View File

@@ -33,7 +33,7 @@ import org.eclipse.hawkbit.ui.common.data.proxies.ProxySoftwareModule;
import org.eclipse.hawkbit.ui.common.event.EntityModifiedEventPayload; import org.eclipse.hawkbit.ui.common.event.EntityModifiedEventPayload;
import org.eclipse.hawkbit.ui.common.event.EntityModifiedEventPayload.EntityModifiedEventType; import org.eclipse.hawkbit.ui.common.event.EntityModifiedEventPayload.EntityModifiedEventType;
import org.eclipse.hawkbit.ui.common.event.EventTopics; import org.eclipse.hawkbit.ui.common.event.EventTopics;
import org.eclipse.hawkbit.ui.utils.SpringContextHelper; import org.eclipse.hawkbit.ui.utils.SpringContextHolder;
import org.eclipse.hawkbit.ui.utils.UINotification; import org.eclipse.hawkbit.ui.utils.UINotification;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -78,9 +78,9 @@ public abstract class AbstractFileTransferHandler implements Serializable {
final Lock uploadLock) { final Lock uploadLock) {
this.artifactManagement = artifactManagement; this.artifactManagement = artifactManagement;
this.i18n = i18n; this.i18n = i18n;
this.eventBus = SpringContextHelper.getBean(EventBus.UIEventBus.class); this.eventBus = SpringContextHolder.getInstance().getBean(EventBus.UIEventBus.class);
this.artifactUploadState = SpringContextHelper.getBean(ArtifactUploadState.class); this.artifactUploadState = SpringContextHolder.getInstance().getBean(ArtifactUploadState.class);
this.uiNotification = SpringContextHelper.getBean(UINotification.class); this.uiNotification = SpringContextHolder.getInstance().getBean(UINotification.class);
this.uploadLock = uploadLock; this.uploadLock = uploadLock;
} }
@@ -103,8 +103,9 @@ public abstract class AbstractFileTransferHandler implements Serializable {
protected void startTransferToRepositoryThread(final InputStream inputStream, final FileUploadId fileUploadId, protected void startTransferToRepositoryThread(final InputStream inputStream, final FileUploadId fileUploadId,
final String mimeType) { final String mimeType) {
SpringContextHelper.getBean("uiExecutor", Executor.class).execute(new TransferArtifactToRepositoryRunnable( SpringContextHolder.getInstance().getBean("uiExecutor", Executor.class)
inputStream, fileUploadId, mimeType, UI.getCurrent(), uploadLock)); .execute(new TransferArtifactToRepositoryRunnable(inputStream, fileUploadId, mimeType, UI.getCurrent(),
uploadLock));
} }
private void interruptUploadAndSetReason(final String failureReason) { private void interruptUploadAndSetReason(final String failureReason) {
@@ -241,15 +242,15 @@ public abstract class AbstractFileTransferHandler implements Serializable {
* Constructor for TransferArtifactToRepositoryRunnable * Constructor for TransferArtifactToRepositoryRunnable
* *
* @param inputStream * @param inputStream
* InputStream * InputStream
* @param fileUploadId * @param fileUploadId
* FileUploadId * FileUploadId
* @param mimeType * @param mimeType
* String * String
* @param vaadinUi * @param vaadinUi
* UI * UI
* @param uploadLock * @param uploadLock
* Lock * Lock
*/ */
public TransferArtifactToRepositoryRunnable(final InputStream inputStream, final FileUploadId fileUploadId, public TransferArtifactToRepositoryRunnable(final InputStream inputStream, final FileUploadId fileUploadId,
final String mimeType, final UI vaadinUi, final Lock uploadLock) { final String mimeType, final UI vaadinUi, final Lock uploadLock) {

View File

@@ -15,7 +15,7 @@ import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.im.authentication.UserPrincipal; import org.eclipse.hawkbit.im.authentication.UserPrincipal;
import org.eclipse.hawkbit.repository.model.BaseEntity; import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.ui.utils.SpringContextHelper; import org.eclipse.hawkbit.ui.utils.SpringContextHolder;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
@@ -179,10 +179,10 @@ public final class UserDetailsFormatter {
public static UserDetails getCurrentUser() { public static UserDetails getCurrentUser() {
final SecurityContext context = (SecurityContext) VaadinService.getCurrentRequest().getWrappedSession() final SecurityContext context = (SecurityContext) VaadinService.getCurrentRequest().getWrappedSession()
.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); .getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
Authentication authentication = context.getAuthentication(); final Authentication authentication = context.getAuthentication();
if (authentication instanceof OAuth2AuthenticationToken) { if (authentication instanceof OAuth2AuthenticationToken) {
OidcUser oidcUser = (OidcUser) authentication.getPrincipal(); final OidcUser oidcUser = (OidcUser) authentication.getPrincipal();
Object details = authentication.getDetails(); final Object details = authentication.getDetails();
String tenant = "DEFAULT"; String tenant = "DEFAULT";
if (details instanceof TenantAwareAuthenticationDetails) { if (details instanceof TenantAwareAuthenticationDetails) {
tenant = ((TenantAwareAuthenticationDetails) details).getTenant(); tenant = ((TenantAwareAuthenticationDetails) details).getTenant();
@@ -207,7 +207,8 @@ public final class UserDetailsFormatter {
// Exception squid:S1166 - exception has to be hidden // Exception squid:S1166 - exception has to be hidden
@SuppressWarnings({ "squid:S1166" }) @SuppressWarnings({ "squid:S1166" })
private static UserDetails loadUserByUsername(final String username) { private static UserDetails loadUserByUsername(final String username) {
final UserDetailsService userDetailsService = SpringContextHelper.getBean(UserDetailsService.class); final UserDetailsService userDetailsService = SpringContextHolder.getInstance()
.getBean(UserDetailsService.class);
try { try {
return userDetailsService.loadUserByUsername(username); return userDetailsService.loadUserByUsername(username);
} catch (final UsernameNotFoundException e) { } catch (final UsernameNotFoundException e) {

View File

@@ -38,6 +38,7 @@ public abstract class AbstractMetaDataWindowLayout<F> extends HorizontalLayout {
protected final VaadinMessageSource i18n; protected final VaadinMessageSource i18n;
protected final transient UIEventBus eventBus; protected final transient UIEventBus eventBus;
protected final UINotification uiNotification; protected final UINotification uiNotification;
protected final SpPermissionChecker permChecker;
private final MetadataWindowGridHeader metadataWindowGridHeader; private final MetadataWindowGridHeader metadataWindowGridHeader;
@@ -62,9 +63,23 @@ public abstract class AbstractMetaDataWindowLayout<F> extends HorizontalLayout {
this.i18n = i18n; this.i18n = i18n;
this.eventBus = eventBus; this.eventBus = eventBus;
this.uiNotification = uiNotification; this.uiNotification = uiNotification;
this.permChecker = permChecker;
this.metadataWindowGridHeader = new MetadataWindowGridHeader(i18n, permChecker, eventBus, this.metadataWindowGridHeader = new MetadataWindowGridHeader(i18n, permChecker, eventBus,
this::showAddMetaDataLayout); this::hasMetadataChangePermission, this::showAddMetaDataLayout);
}
// can be overriden in child classes for entity-specific permission
protected boolean hasMetadataChangePermission() {
return permChecker.hasUpdateRepositoryPermission();
}
private void showAddMetaDataLayout() {
getMetaDataWindowGrid().deselectAll();
getAddMetaDataWindowController().populateWithData(null);
saveCallback.accept(getAddMetaDataWindowController().getSaveDialogCloseListener());
resetSaveButton();
} }
protected MetaData createMetaData(final ProxyMetaData entity) { protected MetaData createMetaData(final ProxyMetaData entity) {
@@ -129,14 +144,6 @@ public abstract class AbstractMetaDataWindowLayout<F> extends HorizontalLayout {
protected abstract void doDeleteMetaDataByKey(final String metaDataKey); protected abstract void doDeleteMetaDataByKey(final String metaDataKey);
private void showAddMetaDataLayout() {
getMetaDataWindowGrid().deselectAll();
getAddMetaDataWindowController().populateWithData(null);
saveCallback.accept(getAddMetaDataWindowController().getSaveDialogCloseListener());
resetSaveButton();
}
/** /**
* @return add meta data window controller * @return add meta data window controller
*/ */

View File

@@ -34,17 +34,17 @@ public class DistributionSetDetailsHeader extends AbstractDetailsHeader<ProxyDis
* Constructor for DistributionSetDetailsHeader * Constructor for DistributionSetDetailsHeader
* *
* @param i18n * @param i18n
* VaadinMessageSource * VaadinMessageSource
* @param permChecker * @param permChecker
* SpPermissionChecker * SpPermissionChecker
* @param eventBus * @param eventBus
* UIEventBus * UIEventBus
* @param uiNotification * @param uiNotification
* UINotification * UINotification
* @param dsWindowBuilder * @param dsWindowBuilder
* DsWindowBuilder * DsWindowBuilder
* @param dsMetaDataWindowBuilder * @param dsMetaDataWindowBuilder
* DsMetaDataWindowBuilder * DsMetaDataWindowBuilder
*/ */
public DistributionSetDetailsHeader(final VaadinMessageSource i18n, final SpPermissionChecker permChecker, public DistributionSetDetailsHeader(final VaadinMessageSource i18n, final SpPermissionChecker permChecker,
final UIEventBus eventBus, final UINotification uiNotification, final DsWindowBuilder dsWindowBuilder, final UIEventBus eventBus, final UINotification uiNotification, final DsWindowBuilder dsWindowBuilder,
@@ -72,11 +72,6 @@ public class DistributionSetDetailsHeader extends AbstractDetailsHeader<ProxyDis
return masterEntity.getNameVersion(); return masterEntity.getNameVersion();
} }
@Override
protected boolean hasEditPermission() {
return permChecker.hasUpdateRepositoryPermission();
}
@Override @Override
protected String getEditIconId() { protected String getEditIconId() {
return UIComponentIdProvider.DS_EDIT_BUTTON; return UIComponentIdProvider.DS_EDIT_BUTTON;

View File

@@ -8,6 +8,8 @@
*/ */
package org.eclipse.hawkbit.ui.common.detailslayout; package org.eclipse.hawkbit.ui.common.detailslayout;
import java.util.function.BooleanSupplier;
import org.eclipse.hawkbit.ui.common.AbstractEntityWindowLayout; import org.eclipse.hawkbit.ui.common.AbstractEntityWindowLayout;
import org.eclipse.hawkbit.ui.common.data.proxies.ProxyMetaData; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyMetaData;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
@@ -21,6 +23,7 @@ import com.vaadin.ui.VerticalLayout;
* Class for metadata add/update window layout. * Class for metadata add/update window layout.
*/ */
public class MetaDataAddUpdateWindowLayout extends AbstractEntityWindowLayout<ProxyMetaData> { public class MetaDataAddUpdateWindowLayout extends AbstractEntityWindowLayout<ProxyMetaData> {
private final BooleanSupplier hasMetadataChangePermission;
protected final MetaDataAddUpdateWindowLayoutComponentBuilder metaDataComponentBuilder; protected final MetaDataAddUpdateWindowLayoutComponentBuilder metaDataComponentBuilder;
private final TextField metadataKey; private final TextField metadataKey;
@@ -30,11 +33,15 @@ public class MetaDataAddUpdateWindowLayout extends AbstractEntityWindowLayout<Pr
* Constructor for AbstractTagWindowLayout * Constructor for AbstractTagWindowLayout
* *
* @param i18n * @param i18n
* I18N * VaadinMessageSource
* @param hasMetadataChangePermission
* checks the permission allowing to change metadata entities
*/ */
public MetaDataAddUpdateWindowLayout(final VaadinMessageSource i18n) { public MetaDataAddUpdateWindowLayout(final VaadinMessageSource i18n,
final BooleanSupplier hasMetadataChangePermission) {
super(); super();
this.hasMetadataChangePermission = hasMetadataChangePermission;
this.metaDataComponentBuilder = new MetaDataAddUpdateWindowLayoutComponentBuilder(i18n); this.metaDataComponentBuilder = new MetaDataAddUpdateWindowLayoutComponentBuilder(i18n);
this.metadataKey = metaDataComponentBuilder.createKeyField(binder); this.metadataKey = metaDataComponentBuilder.createKeyField(binder);
@@ -43,6 +50,16 @@ public class MetaDataAddUpdateWindowLayout extends AbstractEntityWindowLayout<Pr
@Override @Override
public ComponentContainer getRootComponent() { public ComponentContainer getRootComponent() {
final VerticalLayout addUpdateLayout = getMetadataAddUpdateLayout();
if (!hasMetadataChangePermission.getAsBoolean()) {
disableMetadataInputComponents();
}
return addUpdateLayout;
}
protected VerticalLayout getMetadataAddUpdateLayout() {
final VerticalLayout addUpdateLayout = new VerticalLayout(); final VerticalLayout addUpdateLayout = new VerticalLayout();
addUpdateLayout.setSpacing(true); addUpdateLayout.setSpacing(true);
addUpdateLayout.setMargin(false); addUpdateLayout.setMargin(false);
@@ -57,11 +74,18 @@ public class MetaDataAddUpdateWindowLayout extends AbstractEntityWindowLayout<Pr
return addUpdateLayout; return addUpdateLayout;
} }
protected void disableMetadataInputComponents() {
metadataKey.setEnabled(false);
metadataValue.setEnabled(false);
}
/** /**
* Enable meta data key * Enable meta data key
*/ */
public void enableMetadataKey() { public void enableMetadataKey() {
metadataKey.setEnabled(true); if (hasMetadataChangePermission.getAsBoolean()) {
metadataKey.setEnabled(true);
}
} }
/** /**

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.ui.common.detailslayout; package org.eclipse.hawkbit.ui.common.detailslayout;
import java.util.Collection; import java.util.Collection;
import java.util.function.BooleanSupplier;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.SpPermissionChecker;
@@ -38,6 +39,7 @@ public class MetaDataWindowGrid<F> extends AbstractGrid<ProxyMetaData, F> implem
public static final String META_DATA_VALUE_ID = "metaDataValue"; public static final String META_DATA_VALUE_ID = "metaDataValue";
public static final String META_DATA_DELETE_BUTTON_ID = "metaDataDeleteButton"; public static final String META_DATA_DELETE_BUTTON_ID = "metaDataDeleteButton";
private final transient BooleanSupplier hasMetadataChangePermission;
private final transient DeleteSupport<ProxyMetaData> metaDataDeleteSupport; private final transient DeleteSupport<ProxyMetaData> metaDataDeleteSupport;
/** /**
@@ -52,17 +54,20 @@ public class MetaDataWindowGrid<F> extends AbstractGrid<ProxyMetaData, F> implem
* @param notification * @param notification
* UINotification * UINotification
* @param dataProvider * @param dataProvider
* AbstractMetaDataDataProvider for filter support * provides entity-specific metadata entities
* @param hasMetadataChangePermission
* checks the permission allowing to change metadata entities
* @param itemsDeletionCallback * @param itemsDeletionCallback
* Grid item deletion Call back for event listener * callback method to delete metadata entities
* *
*/ */
public MetaDataWindowGrid(final VaadinMessageSource i18n, final UIEventBus eventBus, public MetaDataWindowGrid(final VaadinMessageSource i18n, final UIEventBus eventBus,
final SpPermissionChecker permissionChecker, final UINotification notification, final SpPermissionChecker permissionChecker, final UINotification notification,
final AbstractMetaDataDataProvider<?, F> dataProvider, final AbstractMetaDataDataProvider<?, F> dataProvider, final BooleanSupplier hasMetadataChangePermission,
final Predicate<Collection<ProxyMetaData>> itemsDeletionCallback) { final Predicate<Collection<ProxyMetaData>> itemsDeletionCallback) {
super(i18n, eventBus, permissionChecker); super(i18n, eventBus, permissionChecker);
this.hasMetadataChangePermission = hasMetadataChangePermission;
this.metaDataDeleteSupport = new DeleteSupport<>(this, i18n, notification, "caption.metadata", this.metaDataDeleteSupport = new DeleteSupport<>(this, i18n, notification, "caption.metadata",
"caption.metadata.plur", ProxyMetaData::getKey, itemsDeletionCallback, "caption.metadata.plur", ProxyMetaData::getKey, itemsDeletionCallback,
UIComponentIdProvider.METADATA_DELETE_CONFIRMATION_DIALOG); UIComponentIdProvider.METADATA_DELETE_CONFIRMATION_DIALOG);
@@ -79,7 +84,7 @@ public class MetaDataWindowGrid<F> extends AbstractGrid<ProxyMetaData, F> implem
@Override @Override
public String getGridId() { public String getGridId() {
return UIComponentIdProvider.METDATA_WINDOW_TABLE_ID; return UIComponentIdProvider.METADATA_WINDOW_TABLE_ID;
} }
@Override @Override
@@ -91,7 +96,7 @@ public class MetaDataWindowGrid<F> extends AbstractGrid<ProxyMetaData, F> implem
.setCaption(i18n.getMessage("header.value")).setHidden(true).setHidable(true); .setCaption(i18n.getMessage("header.value")).setHidden(true).setHidable(true);
GridComponentBuilder.addDeleteColumn(this, i18n, META_DATA_DELETE_BUTTON_ID, metaDataDeleteSupport, GridComponentBuilder.addDeleteColumn(this, i18n, META_DATA_DELETE_BUTTON_ID, metaDataDeleteSupport,
UIComponentIdProvider.META_DATA_DELET_ICON, e -> permissionChecker.hasDeleteRepositoryPermission()); UIComponentIdProvider.META_DATA_DELET_ICON, e -> hasMetadataChangePermission.getAsBoolean());
} }
@Override @Override

View File

@@ -83,7 +83,7 @@ public class MetadataDetailsGrid<F> extends AbstractGrid<ProxyMetaData, F> imple
@Override @Override
public String getGridId() { public String getGridId() {
return typePrefix + "." + UIComponentIdProvider.METDATA_DETAILS_TABLE_ID; return typePrefix + "." + UIComponentIdProvider.METADATA_DETAILS_TABLE_ID;
} }
@Override @Override

View File

@@ -8,6 +8,8 @@
*/ */
package org.eclipse.hawkbit.ui.common.detailslayout; package org.eclipse.hawkbit.ui.common.detailslayout;
import java.util.function.BooleanSupplier;
import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.common.grid.header.AbstractGridHeader; import org.eclipse.hawkbit.ui.common.grid.header.AbstractGridHeader;
import org.eclipse.hawkbit.ui.common.grid.header.support.AddHeaderSupport; import org.eclipse.hawkbit.ui.common.grid.header.support.AddHeaderSupport;
@@ -35,15 +37,18 @@ public class MetadataWindowGridHeader extends AbstractGridHeader {
* SpPermissionChecker * SpPermissionChecker
* @param eventBus * @param eventBus
* UIEventBus * UIEventBus
* @param hasMetadataChangePermission
* checks the permission allowing to change metadata entities
* @param addNewItemCallback * @param addNewItemCallback
* Runnable * callback method to add new metadata entities
*/ */
public MetadataWindowGridHeader(final VaadinMessageSource i18n, final SpPermissionChecker permChecker, public MetadataWindowGridHeader(final VaadinMessageSource i18n, final SpPermissionChecker permChecker,
final UIEventBus eventBus, final Runnable addNewItemCallback) { final UIEventBus eventBus, final BooleanSupplier hasMetadataChangePermission,
final Runnable addNewItemCallback) {
super(i18n, permChecker, eventBus); super(i18n, permChecker, eventBus);
if (permChecker.hasCreateRepositoryPermission()) { if (hasMetadataChangePermission.getAsBoolean()) {
this.addHeaderSupport = new AddHeaderSupport(i18n, UIComponentIdProvider.METADTA_ADD_ICON_ID, this.addHeaderSupport = new AddHeaderSupport(i18n, UIComponentIdProvider.METADATA_ADD_ICON_ID,
addNewItemCallback, () -> false); addNewItemCallback, () -> false);
} else { } else {
this.addHeaderSupport = null; this.addHeaderSupport = null;

View File

@@ -95,11 +95,6 @@ public class SoftwareModuleDetailsHeader extends AbstractDetailsHeader<ProxySoft
} }
} }
@Override
protected boolean hasEditPermission() {
return permChecker.hasUpdateRepositoryPermission();
}
@Override @Override
protected String getEditIconId() { protected String getEditIconId() {
return UIComponentIdProvider.UPLOAD_SW_MODULE_EDIT_BUTTON; return UIComponentIdProvider.UPLOAD_SW_MODULE_EDIT_BUTTON;

View File

@@ -21,7 +21,7 @@ import org.vaadin.spring.events.EventBus.UIEventBus;
* Header for entity details with edit and metadata support. * Header for entity details with edit and metadata support.
* *
* @param <T> * @param <T>
* Generic type * Generic type
*/ */
public abstract class AbstractDetailsHeader<T> extends AbstractMasterAwareGridHeader<T> { public abstract class AbstractDetailsHeader<T> extends AbstractMasterAwareGridHeader<T> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -37,13 +37,13 @@ public abstract class AbstractDetailsHeader<T> extends AbstractMasterAwareGridHe
* Constructor for AbstractDetailsHeader * Constructor for AbstractDetailsHeader
* *
* @param i18n * @param i18n
* VaadinMessageSource * VaadinMessageSource
* @param permChecker * @param permChecker
* SpPermissionChecker * SpPermissionChecker
* @param eventBus * @param eventBus
* UIEventBus * UIEventBus
* @param uiNotification * @param uiNotification
* UINotification * UINotification
*/ */
public AbstractDetailsHeader(final VaadinMessageSource i18n, final SpPermissionChecker permChecker, public AbstractDetailsHeader(final VaadinMessageSource i18n, final SpPermissionChecker permChecker,
final UIEventBus eventBus, final UINotification uiNotification) { final UIEventBus eventBus, final UINotification uiNotification) {
@@ -53,17 +53,29 @@ public abstract class AbstractDetailsHeader<T> extends AbstractMasterAwareGridHe
if (hasEditPermission()) { if (hasEditPermission()) {
this.editDetailsHeaderSupport = new EditDetailsHeaderSupport(i18n, getEditIconId(), this::onEdit); this.editDetailsHeaderSupport = new EditDetailsHeaderSupport(i18n, getEditIconId(), this::onEdit);
} else {
this.editDetailsHeaderSupport = null;
}
if (hasMetadataReadPermission()) {
this.metaDataDetailsHeaderSupport = new MetaDataDetailsHeaderSupport(i18n, getMetaDataIconId(), this.metaDataDetailsHeaderSupport = new MetaDataDetailsHeaderSupport(i18n, getMetaDataIconId(),
this::showMetaData); this::showMetaData);
} else { } else {
this.editDetailsHeaderSupport = null;
this.metaDataDetailsHeaderSupport = null; this.metaDataDetailsHeaderSupport = null;
} }
addHeaderSupports(Arrays.asList(editDetailsHeaderSupport, metaDataDetailsHeaderSupport)); addHeaderSupports(Arrays.asList(editDetailsHeaderSupport, metaDataDetailsHeaderSupport));
} }
protected abstract boolean hasEditPermission(); // can be overriden in child classes for entity-specific permission
protected boolean hasEditPermission() {
return permChecker.hasUpdateRepositoryPermission();
}
// can be overriden in child classes for entity-specific permission
protected boolean hasMetadataReadPermission() {
return permChecker.hasReadRepositoryPermission();
}
protected abstract String getEditIconId(); protected abstract String getEditIconId();

View File

@@ -35,13 +35,13 @@ public class AddHeaderSupport implements HeaderSupport {
* Constructor for AddHeaderSupport * Constructor for AddHeaderSupport
* *
* @param i18n * @param i18n
* AddHeaderSupport * VaadinMessageSource
* @param addIconId * @param addIconId
* Add icon id * Add icon id
* @param addItemCallback * @param addItemCallback
* Runnable * callback method to add new entities
* @param maximizedStateSupplier * @param maximizedStateSupplier
* BooleanSupplier * provides the max/min state of the grid
*/ */
public AddHeaderSupport(final VaadinMessageSource i18n, final String addIconId, final Runnable addItemCallback, public AddHeaderSupport(final VaadinMessageSource i18n, final String addIconId, final Runnable addItemCallback,
final BooleanSupplier maximizedStateSupplier) { final BooleanSupplier maximizedStateSupplier) {

View File

@@ -17,7 +17,7 @@ import javax.validation.ConstraintViolationException;
import org.eclipse.hawkbit.ui.common.notification.ParallelNotification; import org.eclipse.hawkbit.ui.common.notification.ParallelNotification;
import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions;
import org.eclipse.hawkbit.ui.utils.SpringContextHelper; import org.eclipse.hawkbit.ui.utils.SpringContextHolder;
import org.eclipse.hawkbit.ui.utils.UINotification; import org.eclipse.hawkbit.ui.utils.UINotification;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -106,7 +106,7 @@ public class HawkbitUIErrorHandler extends DefaultErrorHandler {
LOG.error("Error in UI: ", ex); LOG.error("Error in UI: ", ex);
final String errorMessage = extractMessageFrom(ex); final String errorMessage = extractMessageFrom(ex);
final VaadinMessageSource i18n = SpringContextHelper.getBean(VaadinMessageSource.class); final VaadinMessageSource i18n = SpringContextHolder.getInstance().getBean(VaadinMessageSource.class);
return buildErrorNotification(i18n.getMessage("caption.error"), errorMessage); return buildErrorNotification(i18n.getMessage("caption.error"), errorMessage);
} }

View File

@@ -69,9 +69,9 @@ public class DsMetaDataWindowLayout extends AbstractMetaDataWindowLayout<Long> {
this.entityFactory = entityFactory; this.entityFactory = entityFactory;
this.dsMetaDataWindowGrid = new MetaDataWindowGrid<>(i18n, eventBus, permChecker, uiNotification, this.dsMetaDataWindowGrid = new MetaDataWindowGrid<>(i18n, eventBus, permChecker, uiNotification,
new DsMetaDataDataProvider(dsManagement), this::deleteMetaData); new DsMetaDataDataProvider(dsManagement), this::hasMetadataChangePermission, this::deleteMetaData);
this.metaDataAddUpdateWindowLayout = new MetaDataAddUpdateWindowLayout(i18n); this.metaDataAddUpdateWindowLayout = new MetaDataAddUpdateWindowLayout(i18n, this::hasMetadataChangePermission);
this.addDsMetaDataWindowController = new AddMetaDataWindowController(i18n, uiNotification, this.addDsMetaDataWindowController = new AddMetaDataWindowController(i18n, uiNotification,
metaDataAddUpdateWindowLayout, this::createMetaData, this::isDuplicate); metaDataAddUpdateWindowLayout, this::createMetaData, this::isDuplicate);
this.updateDsMetaDataWindowController = new UpdateMetaDataWindowController(i18n, uiNotification, this.updateDsMetaDataWindowController = new UpdateMetaDataWindowController(i18n, uiNotification,

View File

@@ -21,7 +21,6 @@ import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
import org.eclipse.hawkbit.ui.themes.HawkbitTheme; import org.eclipse.hawkbit.ui.themes.HawkbitTheme;
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions;
import org.eclipse.hawkbit.ui.utils.SpringContextHelper;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import org.eclipse.hawkbit.ui.utils.UINotification; import org.eclipse.hawkbit.ui.utils.UINotification;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
@@ -117,7 +116,6 @@ public abstract class AbstractHawkbitLoginUI extends UI {
@Override @Override
protected void init(final VaadinRequest request) { protected void init(final VaadinRequest request) {
HawkbitCommonUtil.initLocalization(this, uiProperties.getLocalization(), i18n); HawkbitCommonUtil.initLocalization(this, uiProperties.getLocalization(), i18n);
SpringContextHelper.setContext(context);
params = UriComponentsBuilder.fromUri(Page.getCurrent().getLocation()).build().getQueryParams(); params = UriComponentsBuilder.fromUri(Page.getCurrent().getLocation()).build().getQueryParams();

View File

@@ -87,7 +87,7 @@ public class TargetDetails extends AbstractGridDetailsLayout<ProxyTarget> {
new SimpleEntry<>(i18n.getMessage("caption.tags.tab"), getTargetTagToken().getTagPanel()), new SimpleEntry<>(i18n.getMessage("caption.tags.tab"), getTargetTagToken().getTagPanel()),
new SimpleEntry<>(i18n.getMessage("caption.logs.tab"), logDetails))); new SimpleEntry<>(i18n.getMessage("caption.logs.tab"), logDetails)));
if (permissionChecker.hasCreateRepositoryPermission()) { if (permissionChecker.hasReadRepositoryPermission()) {
this.targetMetadataGrid = new MetadataDetailsGrid<>(i18n, eventBus, this.targetMetadataGrid = new MetadataDetailsGrid<>(i18n, eventBus,
UIComponentIdProvider.TARGET_TYPE_PREFIX, this::showMetadataDetails, UIComponentIdProvider.TARGET_TYPE_PREFIX, this::showMetadataDetails,
new TargetMetaDataDataProvider(targetManagement)); new TargetMetaDataDataProvider(targetManagement));

View File

@@ -70,9 +70,10 @@ public class TargetMetaDataWindowLayout extends AbstractMetaDataWindowLayout<Str
this.entityFactory = entityFactory; this.entityFactory = entityFactory;
this.targetMetaDataWindowGrid = new MetaDataWindowGrid<>(i18n, eventBus, permChecker, uiNotification, this.targetMetaDataWindowGrid = new MetaDataWindowGrid<>(i18n, eventBus, permChecker, uiNotification,
new TargetMetaDataDataProvider(targetManagement), this::deleteMetaData); new TargetMetaDataDataProvider(targetManagement), this::hasMetadataChangePermission,
this::deleteMetaData);
this.metaDataAddUpdateWindowLayout = new MetaDataAddUpdateWindowLayout(i18n); this.metaDataAddUpdateWindowLayout = new MetaDataAddUpdateWindowLayout(i18n, this::hasMetadataChangePermission);
this.addTargetMetaDataWindowController = new AddMetaDataWindowController(i18n, uiNotification, this.addTargetMetaDataWindowController = new AddMetaDataWindowController(i18n, uiNotification,
metaDataAddUpdateWindowLayout, this::createMetaData, this::isDuplicate); metaDataAddUpdateWindowLayout, this::createMetaData, this::isDuplicate);
this.updateTargetMetaDataWindowController = new UpdateMetaDataWindowController(i18n, uiNotification, this.updateTargetMetaDataWindowController = new UpdateMetaDataWindowController(i18n, uiNotification,

View File

@@ -131,6 +131,9 @@ public class DelayedEventBusPushStrategy
LOG.debug("Cleanup delayed event push strategy for UI {}", vaadinUI.getUIId()); LOG.debug("Cleanup delayed event push strategy for UI {}", vaadinUI.getUIId());
jobHandle.cancel(true); jobHandle.cancel(true);
queue.clear(); queue.clear();
jobHandle = null;
vaadinUI = null;
} }
private final class DispatchRunnable implements Runnable { private final class DispatchRunnable implements Runnable {

View File

@@ -8,27 +8,31 @@
*/ */
package org.eclipse.hawkbit.ui.utils; package org.eclipse.hawkbit.ui.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
/** /**
* Singleton for the spring application context. * Singleton for the spring application context.
*
*
*
*/ */
public final class SpringContextHelper { public final class SpringContextHolder {
private static ApplicationContext context; private static final SpringContextHolder SINGLETON = new SpringContextHolder();
@Autowired
private ApplicationContext context;
/** /**
* Private Constructor. * Private Constructor.
*/ */
private SpringContextHelper() { private SpringContextHolder() {
// Utility class // Utility class
} }
public static void setContext(final ApplicationContext context) { /**
SpringContextHelper.context = context; * @return the spring context holder singleton instance
*/
public static SpringContextHolder getInstance() {
return SINGLETON;
} }
/** /**
@@ -39,7 +43,7 @@ public final class SpringContextHelper {
* application context * application context
* @return the requested bean * @return the requested bean
*/ */
public static Object getBean(final String beanName) { public Object getBean(final String beanName) {
return context.getBean(beanName); return context.getBean(beanName);
} }
@@ -51,7 +55,7 @@ public final class SpringContextHelper {
* application context * application context
* @return the requested bean * @return the requested bean
*/ */
public static <T> T getBean(final Class<T> beanClazz) { public <T> T getBean(final Class<T> beanClazz) {
return context.getBean(beanClazz); return context.getBean(beanClazz);
} }
@@ -66,8 +70,7 @@ public final class SpringContextHelper {
* application context * application context
* @return the requested bean * @return the requested bean
*/ */
public static <T> T getBean(final String beanName, final Class<T> beanClazz) { public <T> T getBean(final String beanName, final Class<T> beanClazz) {
return context.getBean(beanName, beanClazz); return context.getBean(beanName, beanClazz);
} }
} }

View File

@@ -1244,15 +1244,15 @@ public final class UIComponentIdProvider {
/** /**
* Metadata add icon id. * Metadata add icon id.
*/ */
public static final String METADTA_ADD_ICON_ID = "metadata.add.icon.id"; public static final String METADATA_ADD_ICON_ID = "metadata.add.icon.id";
/** /**
* Metadata details table id. * Metadata details table id.
*/ */
public static final String METDATA_DETAILS_TABLE_ID = "metadata.details.table.id"; public static final String METADATA_DETAILS_TABLE_ID = "metadata.details.table.id";
/** /**
* Metadata window table id. * Metadata window table id.
*/ */
public static final String METDATA_WINDOW_TABLE_ID = "metadata.window.table.id"; public static final String METADATA_WINDOW_TABLE_ID = "metadata.window.table.id";
/** /**
* Distribution set table - Manage metadata id. * Distribution set table - Manage metadata id.

View File

@@ -12,6 +12,7 @@ vaadin.servlet.productionMode=true
vaadin.servlet.urlMapping=/UI/* vaadin.servlet.urlMapping=/UI/*
vaadin.servlet.heartbeatInterval=60 vaadin.servlet.heartbeatInterval=60
vaadin.servlet.closeIdleSessions=false vaadin.servlet.closeIdleSessions=false
server.servlet.session.timeout=300
hawkbit.server.ui.links.documentation.root=https://www.eclipse.org/hawkbit/ hawkbit.server.ui.links.documentation.root=https://www.eclipse.org/hawkbit/
hawkbit.server.ui.links.documentation.security=https://www.eclipse.org/hawkbit/concepts/authorization/ hawkbit.server.ui.links.documentation.security=https://www.eclipse.org/hawkbit/concepts/authorization/