diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MgmtUiConfiguration.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MgmtUiConfiguration.java index a5f53aee9..ab4abbc6e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MgmtUiConfiguration.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MgmtUiConfiguration.java @@ -20,6 +20,7 @@ import org.eclipse.hawkbit.ui.common.data.suppliers.TargetManagementStateDataSup import org.eclipse.hawkbit.ui.error.HawkbitUIErrorHandler; import org.eclipse.hawkbit.ui.error.extractors.ConstraintViolationErrorExtractor; import org.eclipse.hawkbit.ui.error.extractors.EntityNotFoundErrorExtractor; +import org.eclipse.hawkbit.ui.error.extractors.InvalidDistributionSetErrorExtractor; import org.eclipse.hawkbit.ui.error.extractors.UiErrorDetailsExtractor; import org.eclipse.hawkbit.ui.error.extractors.UploadErrorExtractor; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; @@ -49,7 +50,7 @@ public class MgmtUiConfiguration { /** * Permission checker for UI. - * + * * @param permissionService * PermissionService * @@ -63,7 +64,7 @@ public class MgmtUiConfiguration { /** * Utility for Vaadin messages source. - * + * * @param source * Delegate MessageSource * @@ -77,7 +78,7 @@ public class MgmtUiConfiguration { /** * Localized system message provider bean. - * + * * @param uiProperties * UiProperties * @param i18n @@ -93,12 +94,12 @@ public class MgmtUiConfiguration { /** * UI Error handler bean. - * + * * @param i18n * VaadinMessageSource * @param uiErrorDetailsExtractor * ui error details extractors - * + * * @return UI Error handler */ @Bean @@ -110,7 +111,7 @@ public class MgmtUiConfiguration { /** * UI Upload Error details extractor bean. - * + * * @return UI Upload Error details extractor */ @Bean @@ -120,7 +121,7 @@ public class MgmtUiConfiguration { /** * UI ConstraintViolation Error details extractor bean. - * + * * @param i18n * VaadinMessageSource * @return UI ConstraintViolation Error details extractor @@ -132,7 +133,7 @@ public class MgmtUiConfiguration { /** * UI Entity not found Error details extractor bean. - * + * * @param i18n * VaadinMessageSource * @return UI EntityNotFound Error details extractor @@ -142,6 +143,19 @@ public class MgmtUiConfiguration { return new EntityNotFoundErrorExtractor(i18n); } + /** + * Details extractor bean for action not possible because of distribution + * set is invalid. + * + * @param i18n + * VaadinMessageSource + * @return UI invalid distributionset Error details extractor + */ + @Bean + UiErrorDetailsExtractor invalidDistributionSetErrorExtractor(final VaadinMessageSource i18n) { + return new InvalidDistributionSetErrorExtractor(i18n); + } + /** * Vaadin4Spring servlet bean. * @@ -154,7 +168,7 @@ public class MgmtUiConfiguration { /** * UI target entity mapper bean. - * + * * @param i18n * VaadinMessageSource * @return UI target entity mapper @@ -166,7 +180,7 @@ public class MgmtUiConfiguration { /** * UI Management target data supplier bean. - * + * * @param targetManagement * TargetManagement * @param targetToProxyTargetMapper @@ -183,7 +197,7 @@ public class MgmtUiConfiguration { /** * UI Filter target data supplier bean. - * + * * @param targetManagement * TargetManagement * @param targetToProxyTargetMapper diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java index d4514a176..9cc0c8329 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java @@ -64,12 +64,12 @@ public class CommonDialogWindow extends Window { * Different kinds of confirm buttons */ public enum ConfirmStyle { - SAVE, OK + SAVE, OK, CONFIRM, NEXT } /** * Constructor - * + * * @param caption * the caption * @param content @@ -189,7 +189,7 @@ public class CommonDialogWindow extends Window { /** * Hide the line that explains the mandatory decorator - * + * */ public void hideMandatoryExplanation() { if (mandatoryLabel != null) { @@ -218,6 +218,14 @@ public class CommonDialogWindow extends Window { confirmButton = SPUIComponentProvider.getButton(UIComponentIdProvider.SAVE_BUTTON, i18n.getMessage(UIMessageIdProvider.BUTTON_SAVE), "", "", true, VaadinIcons.HARDDRIVE, buttonDecorator); + } else if (confirmStyle == ConfirmStyle.NEXT) { + confirmButton = SPUIComponentProvider.getButton(UIComponentIdProvider.OK_BUTTON, + i18n.getMessage(UIMessageIdProvider.BUTTON_NEXT), "", ValoTheme.BUTTON_PRIMARY, false, null, + buttonDecorator); + } else if (confirmStyle == ConfirmStyle.CONFIRM) { + confirmButton = SPUIComponentProvider.getButton(UIComponentIdProvider.OK_BUTTON, + i18n.getMessage(UIMessageIdProvider.BUTTON_CONFIRM), "", ValoTheme.BUTTON_PRIMARY, false, null, + buttonDecorator); } else { confirmButton = SPUIComponentProvider.getButton(UIComponentIdProvider.OK_BUTTON, i18n.getMessage(UIMessageIdProvider.BUTTON_OK), "", ValoTheme.BUTTON_PRIMARY, false, null, diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/StatusIconBuilder.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/StatusIconBuilder.java index 454d2746a..3691b6744 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/StatusIconBuilder.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/StatusIconBuilder.java @@ -1,4 +1,4 @@ -/** +/** * Copyright (c) 2020 Bosch.IO GmbH and others. * * All rights reserved. This program and the accompanying materials @@ -42,7 +42,7 @@ import com.vaadin.server.FontIcon; import com.vaadin.ui.Label; /** - * + * * Generate labels with icons according to entities' states * */ @@ -63,7 +63,7 @@ public final class StatusIconBuilder { /** * constructor - * + * * @param i18n * message source for internationalization * @param getEntityStatus @@ -101,7 +101,7 @@ public final class StatusIconBuilder { /** * constructor - * + * * @param i18n * message source for internationalization * @param getEntityStatus @@ -140,7 +140,7 @@ public final class StatusIconBuilder { /** * constructor - * + * * @param i18n * message source for internationalization * @param getEntityStatus @@ -177,7 +177,7 @@ public final class StatusIconBuilder { /** * constructor - * + * * @param i18n * message source for internationalization * @param getEntityStatus @@ -208,7 +208,7 @@ public final class StatusIconBuilder { if (optionalIcon.isPresent()) { icon = getFontIconFromStatusMap(optionalIcon.get(), getEntityStatus.apply(entity), group.orElse(null)); } else { - icon = buildDefaultStatusIcon(group.orElse(null)); + icon = buildStatusIcon(group.orElse(null)); } return getLabel(entity, icon); @@ -233,7 +233,7 @@ public final class StatusIconBuilder { // Actions are not created for targets when rollout's status is // READY and when duplicate assignment is done. In these cases // display a appropriate status with description - private ProxyFontIcon buildDefaultStatusIcon(final RolloutGroup rolloutGroup) { + private ProxyFontIcon buildStatusIcon(final RolloutGroup rolloutGroup) { if (rolloutGroup != null && rolloutGroup.getStatus() == RolloutGroupStatus.READY) { return new ProxyFontIcon(VaadinIcons.BULLSEYE, SPUIStyleDefinitions.STATUS_ICON_LIGHT_BLUE, i18n.getMessage(UIMessageIdProvider.TOOLTIP_ROLLOUT_GROUP_STATUS_PREFIX @@ -241,9 +241,15 @@ public final class StatusIconBuilder { } else if (rolloutGroup != null && rolloutGroup.getStatus() == RolloutGroupStatus.FINISHED) { final DistributionSet dist = rolloutGroup.getRollout().getDistributionSet(); final String ds = HawkbitCommonUtil.getFormattedNameVersion(dist.getName(), dist.getVersion()); - - return new ProxyFontIcon(VaadinIcons.MINUS_CIRCLE, SPUIStyleDefinitions.STATUS_ICON_BLUE, - i18n.getMessage("message.dist.already.assigned", ds)); + if (dist.isValid()) { + return new ProxyFontIcon(VaadinIcons.MINUS_CIRCLE, SPUIStyleDefinitions.STATUS_ICON_BLUE, + i18n.getMessage(UIMessageIdProvider.MESSAGE_DISTRIBUTION_ASSIGNED, ds)); + } else { + // invalidated ds, finished rollout but ds wasn't assigned + // to target + return new ProxyFontIcon(VaadinIcons.BAN, SPUIStyleDefinitions.STATUS_ICON_BLUE, + i18n.getMessage(UIMessageIdProvider.MESSAGE_DISTRIBUTION_NOT_ASSIGNED, ds)); + } } else { return generateUnknwonStateIcon(); } @@ -264,7 +270,7 @@ public final class StatusIconBuilder { /** * constructor - * + * * @param i18n * message source for internationalization * @param getEntityStatus @@ -298,7 +304,7 @@ public final class StatusIconBuilder { /** * constructor - * + * * @param i18n * message source for internationalization * @param getEntityStatus @@ -333,7 +339,7 @@ public final class StatusIconBuilder { /** * constructor - * + * * @param i18n * message source for internationalization * @param getEntityStatus @@ -366,7 +372,7 @@ public final class StatusIconBuilder { /** * constructor - * + * * @param i18n * message source for internationalization * @param getEntityStatus @@ -393,7 +399,7 @@ public final class StatusIconBuilder { /** * constructor - * + * * @param i18n * message source for internationalization * @param labelIdPrefix @@ -424,7 +430,7 @@ public final class StatusIconBuilder { /** * constructor - * + * * @param i18n * message source for internationalization * @param labelIdPrefix @@ -541,7 +547,7 @@ public final class StatusIconBuilder { /** * Generate a label from the entity according to its state - * + * * @param entity * to read the state from * @return the label diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/DistributionSetToProxyDistributionMapper.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/DistributionSetToProxyDistributionMapper.java index a356d2f48..ce60bd81d 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/DistributionSetToProxyDistributionMapper.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/DistributionSetToProxyDistributionMapper.java @@ -32,6 +32,7 @@ public class DistributionSetToProxyDistributionMapper HawkbitCommonUtil.getFormattedNameVersion(distributionSet.getName(), distributionSet.getVersion())); proxyDistribution.setIsComplete(distributionSet.isComplete()); proxyDistribution.setRequiredMigrationStep(distributionSet.isRequiredMigrationStep()); + proxyDistribution.setIsValid(distributionSet.isValid()); final DistributionSetType type = distributionSet.getType(); final ProxyTypeInfo typeInfo = new ProxyTypeInfo(type.getId(), type.getName(), type.getKey()); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/RolloutToProxyRolloutMapper.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/RolloutToProxyRolloutMapper.java index e55ff1e42..b29d8564a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/RolloutToProxyRolloutMapper.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/RolloutToProxyRolloutMapper.java @@ -26,7 +26,7 @@ public class RolloutToProxyRolloutMapper extends AbstractNamedEntityToProxyNamed mapNamedEntityAttributes(rollout, proxyRollout); final DistributionSet ds = rollout.getDistributionSet(); - proxyRollout.setDsInfo(new ProxyDistributionSetInfo(ds.getId(), ds.getName(), ds.getVersion())); + proxyRollout.setDsInfo(new ProxyDistributionSetInfo(ds.getId(), ds.getName(), ds.getVersion(), ds.isValid())); proxyRollout .setNumberOfGroups(rollout.getRolloutGroupsCreated() > 0 ? rollout.getRolloutGroupsCreated() : null); proxyRollout.setForcedTime(rollout.getForcedTime() > 0 ? rollout.getForcedTime() : null); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/TargetFilterQueryToProxyTargetFilterMapper.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/TargetFilterQueryToProxyTargetFilterMapper.java index 5022eeb02..00e8a212e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/TargetFilterQueryToProxyTargetFilterMapper.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/mappers/TargetFilterQueryToProxyTargetFilterMapper.java @@ -38,7 +38,7 @@ public class TargetFilterQueryToProxyTargetFilterMapper if (distributionSet != null) { proxyTargetFilter.setAutoAssignmentEnabled(true); proxyTargetFilter.setDistributionSetInfo(new ProxyDistributionSetInfo(distributionSet.getId(), - distributionSet.getName(), distributionSet.getVersion())); + distributionSet.getName(), distributionSet.getVersion(), distributionSet.isValid())); proxyTargetFilter.setAutoAssignActionType(targetFilterQuery.getAutoAssignActionType()); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/DistributionSetStatelessDataProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/DistributionSetStatelessDataProvider.java index 962b54236..88175096f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/DistributionSetStatelessDataProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/providers/DistributionSetStatelessDataProvider.java @@ -49,7 +49,7 @@ public class DistributionSetStatelessDataProvider @Override protected Page loadBackendEntities(final PageRequest pageRequest, final String filter) { final DistributionSetFilterBuilder builder = new DistributionSetFilterBuilder().setIsDeleted(false) - .setIsComplete(true); + .setIsComplete(true).setIsValid(true); if (!StringUtils.isEmpty(filter)) { builder.setFilterString(filter); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSet.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSet.java index 9142f23c8..2bc624309 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSet.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSet.java @@ -28,6 +28,8 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa private boolean requiredMigrationStep; + private Boolean isValid; + /** * Default constructor */ @@ -84,6 +86,28 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa this.isComplete = isComplete; } + /** + + * Flag that indicates if the distribution set is valid. + * + * @return true if the distribution set is valid, otherwise + * false + */ + public Boolean getIsValid() { + return isValid; + } + + /** + * Sets the flag that indicates if the distribution set is valid + * + * @param isValid + * true if the distribution set is valid, otherwise + * false + */ + public void setIsValid(final Boolean isValid) { + this.isValid = isValid; + } + /** * Flag that indicates if the migration step is required. * @@ -140,6 +164,7 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa ds.setName(dsInfo.getName()); ds.setVersion(dsInfo.getVersion()); ds.setNameVersion(dsInfo.getNameVersion()); + ds.setIsValid(dsInfo.isValid()); return ds; } @@ -150,6 +175,6 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa * @return proxy of Id, Name and version */ public ProxyDistributionSetInfo getInfo() { - return new ProxyDistributionSetInfo(getId(), getName(), getVersion()); + return new ProxyDistributionSetInfo(getId(), getName(), getVersion(), getIsValid()); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSetInfo.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSetInfo.java index 274aac3f6..05f040a73 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSetInfo.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/data/proxies/ProxyDistributionSetInfo.java @@ -21,6 +21,7 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity { private String name; private String version; private String nameVersion; + private boolean isValid; /** * Constructor @@ -31,7 +32,7 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity { /** * Constructor - * + * * @param id * distribution set ID * @param name @@ -39,11 +40,12 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity { * @param version * distribution set version */ - public ProxyDistributionSetInfo(final Long id, final String name, final String version) { + public ProxyDistributionSetInfo(final Long id, final String name, final String version, final boolean isValid) { super(id); this.name = name; this.version = version; + this.isValid = isValid; this.nameVersion = HawkbitCommonUtil.getFormattedNameVersion(name, version); } @@ -71,11 +73,19 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity { this.nameVersion = nameVersion; } + public boolean isValid() { + return isValid; + } + + public void setValid(final boolean isValid) { + this.isValid = isValid; + } + @Override public int hashCode() { // nameVersion is ignored because it is a composition of name and // version - return Objects.hash(getId(), getName(), getVersion()); + return Objects.hash(getId(), getName(), getVersion(), isValid()); } @Override @@ -91,6 +101,7 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity { // nameVersion is ignored because it is a composition of name and // version return Objects.equals(this.getId(), other.getId()) && Objects.equals(this.getName(), other.getName()) - && Objects.equals(this.getVersion(), other.getVersion()); + && Objects.equals(this.getVersion(), other.getVersion()) + && Objects.equals(this.isValid(), other.isValid()); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/SoftwareModuleDetailsGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/SoftwareModuleDetailsGrid.java index eeec3583d..cb633cf49 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/SoftwareModuleDetailsGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/SoftwareModuleDetailsGrid.java @@ -184,7 +184,7 @@ public class SoftwareModuleDetailsGrid extends Grid if (smId != null && !StringUtils.isEmpty(smNameVersion)) { smLabelWithUnassignButtonLayout.addComponent(buildSmLabel(smId, smNameVersion)); - if (isUnassignSmAllowed && permissionChecker.hasUpdateRepositoryPermission()) { + if (isUnassignSmAllowed && permissionChecker.hasUpdateRepositoryPermission() && masterEntity.getIsValid()) { smLabelWithUnassignButtonLayout.addComponent(buildSmUnassignButton(smId, smNameVersion)); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/distributionset/AbstractDsGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/distributionset/AbstractDsGrid.java index ac1faed73..e59d5670e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/distributionset/AbstractDsGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/distributionset/AbstractDsGrid.java @@ -29,6 +29,7 @@ import org.eclipse.hawkbit.ui.common.grid.support.DeleteSupport; import org.eclipse.hawkbit.ui.common.grid.support.SelectionSupport; import org.eclipse.hawkbit.ui.common.state.GridLayoutUiState; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; import org.eclipse.hawkbit.ui.utils.UINotification; import com.vaadin.ui.Button; @@ -114,11 +115,30 @@ public abstract class AbstractDsGrid extends AbstractG } protected Column addNameColumn() { - return GridComponentBuilder.addNameColumn(this, i18n, DS_NAME_ID); + final Column nameColumn = GridComponentBuilder.addNameColumn(this, i18n, + DS_NAME_ID); + nameColumn.setDescriptionGenerator(this::createTooltipText); + return nameColumn; + } + + protected String createTooltipText(final ProxyDistributionSet distributionSet) { + final StringBuilder tooltipText = new StringBuilder(distributionSet.getNameVersion()); + if (!distributionSet.getIsComplete()) { + tooltipText.append(" - "); + tooltipText.append(i18n.getMessage(UIMessageIdProvider.TOOLTIP_DISTRIBUTIONSET_INCOMPLETE)); + } + if (!distributionSet.getIsValid()) { + tooltipText.append(" - "); + tooltipText.append(i18n.getMessage(UIMessageIdProvider.TOOLTIP_DISTRIBUTIONSET_INVALID)); + } + return tooltipText.toString(); } protected Column addVersionColumn() { - return GridComponentBuilder.addVersionColumn(this, i18n, ProxyDistributionSet::getVersion, DS_VERSION_ID); + final Column versionColumn = GridComponentBuilder.addVersionColumn(this, i18n, + ProxyDistributionSet::getVersion, DS_VERSION_ID); + versionColumn.setDescriptionGenerator(this::createTooltipText); + return versionColumn; } protected Column addDeleteColumn() { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/distributionset/DistributionSetDetailsHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/distributionset/DistributionSetDetailsHeader.java index 1d4473972..179a73a74 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/distributionset/DistributionSetDetailsHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/distributionset/DistributionSetDetailsHeader.java @@ -66,6 +66,11 @@ public class DistributionSetDetailsHeader extends AbstractDetailsHeader extends AbstractMasterAwareGridHe return permChecker.hasUpdateRepositoryPermission(); } + protected boolean editSelectedEntityAllowed() { + return true; + } + // can be overriden in child classes for entity-specific permission protected boolean hasMetadataReadPermission() { return permChecker.hasReadRepositoryPermission(); @@ -107,6 +111,8 @@ public abstract class AbstractDetailsHeader extends AbstractMasterAwareGridHe public void masterEntityChanged(final T entity) { super.masterEntityChanged(entity); + selectedEntity = entity; + if (entity == null) { disableEdit(); disableMetaData(); @@ -115,7 +121,6 @@ public abstract class AbstractDetailsHeader extends AbstractMasterAwareGridHe enableMetaData(); } - selectedEntity = entity; } private void disableEdit() { @@ -131,13 +136,13 @@ public abstract class AbstractDetailsHeader extends AbstractMasterAwareGridHe } private void enableEdit() { - if (editDetailsHeaderSupport != null) { + if (editDetailsHeaderSupport != null && editSelectedEntityAllowed()) { editDetailsHeaderSupport.enableEditIcon(); } } private void enableMetaData() { - if (metaDataDetailsHeaderSupport != null) { + if (metaDataDetailsHeaderSupport != null && editSelectedEntityAllowed()) { metaDataDetailsHeaderSupport.enableMetaDataIcon(); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/AssignmentSupport.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/AssignmentSupport.java index 36d8237b3..99a522abd 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/AssignmentSupport.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/AssignmentSupport.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.ui.common.grid.support.assignment; +import java.util.ArrayList; import java.util.List; import org.eclipse.hawkbit.repository.model.AbstractAssignmentResult; @@ -18,7 +19,7 @@ import org.springframework.util.CollectionUtils; /** * Support for assigning the items between two grids. - * + * * @param * The item-type of source items * @param @@ -27,6 +28,7 @@ import org.springframework.util.CollectionUtils; public abstract class AssignmentSupport { protected final UINotification notification; protected final VaadinMessageSource i18n; + protected final List lastSpecificErrorMessages = new ArrayList<>(); protected AssignmentSupport(final UINotification notification, final VaadinMessageSource i18n) { this.notification = notification; @@ -46,7 +48,7 @@ public abstract class AssignmentSupport { if (sourceItemsToAssign instanceof List) { assignSourceItemsToTargetItem((List) sourceItemsToAssign, targetItem); } else { - showGenericErrorNotification(); + showErrorNotification(); } } @@ -60,26 +62,35 @@ public abstract class AssignmentSupport { */ public void assignSourceItemsToTargetItem(final List sourceItemsToAssign, final T targetItem) { if (sourceItemsToAssign.isEmpty()) { - showGenericErrorNotification(); + showErrorNotification(); return; } final List filteredSourceItems = getFilteredSourceItems(sourceItemsToAssign, targetItem); if (filteredSourceItems.isEmpty()) { - showGenericErrorNotification(); + showErrorNotification(); return; } performAssignment(filteredSourceItems, targetItem); } - private void showGenericErrorNotification() { - notification.displayValidationError(i18n.getMessage("message.action.did.not.work")); + private void showErrorNotification() { + if (lastSpecificErrorMessages.isEmpty()) { + notification.displayValidationError(i18n.getMessage("message.action.did.not.work")); + } else { + lastSpecificErrorMessages.forEach(notification::displayValidationError); + lastSpecificErrorMessages.clear(); + } + } + + protected void addSpecificValidationErrorMessage(final String errorMessage) { + lastSpecificErrorMessages.add(errorMessage); } /** * Can be overriden in child classes in order to filter source items list. - * + * * @param targetItem * may be used for further filtering of source items */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/DistributionSetsToTargetAssignmentSupport.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/DistributionSetsToTargetAssignmentSupport.java index 49276748b..9e6b24aaf 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/DistributionSetsToTargetAssignmentSupport.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/DistributionSetsToTargetAssignmentSupport.java @@ -23,6 +23,7 @@ import org.eclipse.hawkbit.ui.common.data.proxies.ProxyDistributionSet; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTarget; import org.eclipse.hawkbit.ui.management.miscs.DeploymentAssignmentWindowController; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; /** * Support for assigning the distribution sets to target. @@ -64,9 +65,27 @@ public class DistributionSetsToTargetAssignmentSupport @Override protected List getFilteredSourceItems(final List sourceItemsToAssign, final ProxyTarget targetItem) { + + if (!areSourceDsValid(sourceItemsToAssign)) { + return Collections.emptyList(); + } + return isMultiAssignmentEnabled() ? sourceItemsToAssign : Collections.singletonList(sourceItemsToAssign.get(0)); } + private boolean areSourceDsValid(final List sourceItemsToAssign) { + return sourceItemsToAssign.stream().allMatch(this::isSourceDsValid); + } + + private boolean isSourceDsValid(final ProxyDistributionSet distributionSet) { + if (!distributionSet.getIsValid()) { + addSpecificValidationErrorMessage(i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_DISTRIBUTIONSET_INVALID, + distributionSet.getNameVersion())); + return false; + } + return true; + } + private boolean isMultiAssignmentEnabled() { return systemSecurityContext.runAsSystem(() -> configManagement .getConfigurationValue(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue()); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/SwModulesToDistributionSetAssignmentSupport.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/SwModulesToDistributionSetAssignmentSupport.java index cb3d458ac..077da07b7 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/SwModulesToDistributionSetAssignmentSupport.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/SwModulesToDistributionSetAssignmentSupport.java @@ -31,6 +31,7 @@ import org.eclipse.hawkbit.ui.common.event.EntityModifiedEventPayload.EntityModi import org.eclipse.hawkbit.ui.common.event.EventTopics; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; import org.eclipse.hawkbit.ui.utils.UINotification; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.springframework.util.CollectionUtils; @@ -38,7 +39,7 @@ import org.vaadin.spring.events.EventBus.UIEventBus; /** * Support for assigning software modules to distribution set. - * + * */ public class SwModulesToDistributionSetAssignmentSupport extends DeploymentAssignmentSupport { @@ -110,18 +111,25 @@ public class SwModulesToDistributionSetAssignmentSupport private boolean isTargetDsValid(final ProxyDistributionSet ds, final DistributionSetType dsType) { if (dsType == null) { - notification.displayValidationError(i18n.getMessage("message.dist.type.notfound", ds.getNameVersion())); + addSpecificValidationErrorMessage(i18n.getMessage("message.dist.type.notfound", ds.getNameVersion())); + return false; + } + + if (!ds.getIsValid()) { + /* Distribution is invalidated */ + addSpecificValidationErrorMessage( + i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_DISTRIBUTIONSET_INVALID, ds.getNameVersion())); return false; } if (targetManagement.existsByInstalledOrAssignedDistributionSet(ds.getId())) { /* Distribution is already assigned/installed */ - notification.displayValidationError(i18n.getMessage("message.dist.inuse", ds.getNameVersion())); + addSpecificValidationErrorMessage(i18n.getMessage("message.dist.inuse", ds.getNameVersion())); return false; } if (dsManagement.isInUse(ds.getId())) { - notification.displayValidationError( + addSpecificValidationErrorMessage( i18n.getMessage("message.error.notification.ds.target.assigned", ds.getName(), ds.getVersion())); return false; } @@ -137,8 +145,8 @@ public class SwModulesToDistributionSetAssignmentSupport private boolean checkDuplicateSmToDsAssignment(final ProxySoftwareModule sm, final ProxyDistributionSet ds, final Collection smIdsAlreadyAssignedToDs) { if (!CollectionUtils.isEmpty(smIdsAlreadyAssignedToDs) && smIdsAlreadyAssignedToDs.contains(sm.getId())) { - notification.displayValidationError(i18n.getMessage("message.software.dist.already.assigned", - sm.getNameAndVersion(), ds.getNameVersion())); + addSpecificValidationErrorMessage(i18n.getMessage("message.software.dist.already.assigned", sm.getNameAndVersion(), + ds.getNameVersion())); return false; } @@ -150,9 +158,8 @@ public class SwModulesToDistributionSetAssignmentSupport if (!dsType.containsModuleType(sm.getTypeInfo().getId())) { final String smTypeName = smTypeManagement.get(sm.getTypeInfo().getId()).map(SoftwareModuleType::getName) .orElse(""); - - notification.displayValidationError(i18n.getMessage("message.software.dist.type.notallowed", - sm.getNameAndVersion(), ds.getNameVersion(), smTypeName)); + addSpecificValidationErrorMessage(i18n.getMessage("message.software.dist.type.notallowed", sm.getNameAndVersion(), + ds.getNameVersion(), smTypeName)); return false; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToDistributionSetAssignmentSupport.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToDistributionSetAssignmentSupport.java index 5616bebe7..457a32bec 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToDistributionSetAssignmentSupport.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToDistributionSetAssignmentSupport.java @@ -14,12 +14,13 @@ import java.util.stream.Collectors; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.ui.SpPermissionChecker; -import org.eclipse.hawkbit.ui.common.ConfirmationDialog; import org.eclipse.hawkbit.ui.common.CommonUiDependencies; +import org.eclipse.hawkbit.ui.common.ConfirmationDialog; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyDistributionSet; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTarget; import org.eclipse.hawkbit.ui.management.miscs.DeploymentAssignmentWindowController; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; /** * Support for assigning targets to distribution set. @@ -53,6 +54,25 @@ public class TargetsToDistributionSetAssignmentSupport : Collections.singletonList(SpPermission.UPDATE_TARGET); } + @Override + protected List getFilteredSourceItems(final List sourceItemsToAssign, + final ProxyDistributionSet targetItem) { + if (!isTargetDsValid(targetItem)) { + return Collections.emptyList(); + } + + return sourceItemsToAssign; + } + + private boolean isTargetDsValid(final ProxyDistributionSet distributionSet) { + if (!distributionSet.getIsValid()) { + addSpecificValidationErrorMessage(i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_DISTRIBUTIONSET_INVALID, + distributionSet.getNameVersion())); + return false; + } + return true; + } + @Override protected void performAssignment(final List sourceItemsToAssign, final ProxyDistributionSet targetItem) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetGrid.java index dfbdecf9f..8fbfa748f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetGrid.java @@ -88,7 +88,7 @@ public class DistributionSetGrid extends AbstractDsGrid ds.getIsComplete() ? null : SPUIDefinitions.DISABLE_DISTRIBUTION); + private void initStyleGenerator() { + setStyleGenerator(DistributionSetGrid::getRowStyle); + } + + private static String getRowStyle(final ProxyDistributionSet ds) { + final StringBuilder style = new StringBuilder(); + if (!ds.getIsComplete()) { + style.append(SPUIDefinitions.INCOMPLETE_DISTRIBUTION); + } + if (!ds.getIsValid()) { + style.append(" "); + style.append(SPUIDefinitions.INVALID_DISTRIBUTION); + } + return style.toString(); } @Override diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/HawkbitUIErrorHandler.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/HawkbitUIErrorHandler.java index fe98c366d..265f591a6 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/HawkbitUIErrorHandler.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/HawkbitUIErrorHandler.java @@ -16,6 +16,7 @@ import java.util.stream.Collectors; import org.eclipse.hawkbit.ui.common.notification.ParallelNotification; import org.eclipse.hawkbit.ui.error.extractors.UiErrorDetailsExtractor; import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; import org.eclipse.hawkbit.ui.utils.UINotification; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.slf4j.Logger; @@ -76,7 +77,7 @@ public class HawkbitUIErrorHandler implements ErrorHandler { /** * Method to find the {@link Page} to show notification on. - * + * * @param event * error event * @return current {@link Page} for error notification @@ -117,8 +118,8 @@ public class HawkbitUIErrorHandler implements ErrorHandler { private void showGenericErrorNotification(final Page page, final ErrorEvent event) { LOG.error("Unexpected Ui error occured", event.getThrowable()); - final Notification notification = buildErrorNotification(i18n.getMessage("caption.error"), - i18n.getMessage("message.error")); + final Notification notification = buildErrorNotification(i18n.getMessage(UIMessageIdProvider.CAPTION_ERROR), + i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR)); showErrorNotification(page, notification); } @@ -129,7 +130,7 @@ public class HawkbitUIErrorHandler implements ErrorHandler { /** * Method to build an error notification based on caption and description. - * + * * @param caption * notification caption * @param description @@ -143,7 +144,7 @@ public class HawkbitUIErrorHandler implements ErrorHandler { /** * Method to show notification on the given page. - * + * * @param page * page to show notification on * @param notification diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/extractors/ConstraintViolationErrorExtractor.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/extractors/ConstraintViolationErrorExtractor.java index 00fd4ac60..91196890c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/extractors/ConstraintViolationErrorExtractor.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/extractors/ConstraintViolationErrorExtractor.java @@ -17,6 +17,7 @@ import javax.validation.ConstraintViolationException; import org.apache.commons.lang3.StringUtils; import org.eclipse.hawkbit.ui.error.UiErrorDetails; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.springframework.util.CollectionUtils; @@ -43,7 +44,8 @@ public class ConstraintViolationErrorExtractor extends AbstractSingleUiErrorDeta getViolationsDescription(ex).ifPresent(violationsDescription -> descriptionBuilder.append(":") .append(System.lineSeparator()).append(violationsDescription)); - return UiErrorDetails.create(i18n.getMessage("caption.error"), descriptionBuilder.toString()); + return UiErrorDetails.create(i18n.getMessage(UIMessageIdProvider.CAPTION_ERROR), + descriptionBuilder.toString()); }); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/extractors/InvalidDistributionSetErrorExtractor.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/extractors/InvalidDistributionSetErrorExtractor.java new file mode 100644 index 000000000..7684d7074 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/error/extractors/InvalidDistributionSetErrorExtractor.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.error.extractors; + +import java.util.Optional; + +import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; +import org.eclipse.hawkbit.ui.error.UiErrorDetails; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; + +/** + * UI error details extractor for {@link InvalidDistributionSetException}. + */ +public class InvalidDistributionSetErrorExtractor extends AbstractSingleUiErrorDetailsExtractor { + private final VaadinMessageSource i18n; + + /** + * Constructor for {@link InvalidDistributionSetException}. + * + * @param i18n + * Message source used for localization + */ + public InvalidDistributionSetErrorExtractor(final VaadinMessageSource i18n) { + this.i18n = i18n; + } + + @Override + protected Optional findDetails(final Throwable error) { + return findExceptionOf(error, InvalidDistributionSetException.class) + .map(ex -> UiErrorDetails.create(i18n.getMessage(UIMessageIdProvider.CAPTION_ERROR), + i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_DISTRIBUTIONSET_INVALID, ""))); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java index c0adaa8e9..0324eecfe 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.concurrent.Executor; import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; @@ -88,8 +89,9 @@ public class DeploymentView extends AbstractEventListenersAwareView implements B final UINotification uiNotification, final ManagementUIState managementUIState, final DeploymentManagement deploymentManagement, final DistributionSetManagement distributionSetManagement, final SoftwareModuleManagement smManagement, - final DistributionSetTypeManagement distributionSetTypeManagement, final TargetManagement targetManagement, - final EntityFactory entityFactory, final UiProperties uiProperties, + final DistributionSetTypeManagement distributionSetTypeManagement, + final DistributionSetInvalidationManagement dsInvalidationManagement, + final TargetManagement targetManagement, final EntityFactory entityFactory, final UiProperties uiProperties, final TargetTagManagement targetTagManagement, final DistributionSetTagManagement distributionSetTagManagement, final TargetFilterQueryManagement targetFilterQueryManagement, final SystemManagement systemManagement, @@ -130,7 +132,7 @@ public class DeploymentView extends AbstractEventListenersAwareView implements B this.distributionTagLayout = new DistributionTagLayout(uiDependencies, distributionSetTagManagement, distributionSetManagement, managementUIState.getDistributionTagLayoutUiState()); this.distributionGridLayout = new DistributionGridLayout(uiDependencies, targetManagement, - distributionSetManagement, smManagement, distributionSetTypeManagement, + distributionSetManagement, dsInvalidationManagement, smManagement, distributionSetTypeManagement, distributionSetTagManagement, systemManagement, deploymentManagement, configManagement, systemSecurityContext, uiProperties, managementUIState.getDistributionGridLayoutUiState(), managementUIState.getDistributionTagLayoutUiState(), diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionGrid.java index ae734bc0c..92116bd62 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionGrid.java @@ -16,6 +16,7 @@ import java.util.Map; import java.util.Optional; import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -42,6 +43,7 @@ import org.eclipse.hawkbit.ui.common.grid.support.assignment.TargetsToDistributi import org.eclipse.hawkbit.ui.common.state.TagFilterLayoutUiState; import org.eclipse.hawkbit.ui.management.miscs.DeploymentAssignmentWindowController; import org.eclipse.hawkbit.ui.management.targettable.TargetGridLayoutUiState; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; @@ -58,6 +60,7 @@ public class DistributionGrid extends AbstractDsGrid { private static final long serialVersionUID = 1L; private static final String DS_PIN_BUTTON_ID = "dsPinnButton"; + private static final String DS_INVALIDATE_BUTTON_ID = "dsInvalidateButton"; private final TargetGridLayoutUiState targetGridLayoutUiState; private final DistributionGridLayoutUiState distributionGridLayoutUiState; @@ -67,6 +70,8 @@ public class DistributionGrid extends AbstractDsGrid { private final transient PinSupport pinSupport; + private final transient InvalidateDistributionSetSupport invalidateDistributionSetSupport; + /** * Constructor for DistributionGrid * @@ -76,6 +81,8 @@ public class DistributionGrid extends AbstractDsGrid { * TargetManagement * @param distributionSetManagement * DistributionSetManagement + * @param dsInvalidationManagement + * {@link DistributionSetInvalidationManagement} * @param deploymentManagement * DeploymentManagement * @param uiProperties @@ -88,8 +95,10 @@ public class DistributionGrid extends AbstractDsGrid { * TagFilterLayoutUiState */ public DistributionGrid(final CommonUiDependencies uiDependencies, final TargetManagement targetManagement, - final DistributionSetManagement distributionSetManagement, final DeploymentManagement deploymentManagement, - final UiProperties uiProperties, final DistributionGridLayoutUiState distributionGridLayoutUiState, + final DistributionSetManagement distributionSetManagement, + final DistributionSetInvalidationManagement dsInvalidationManagement, + final DeploymentManagement deploymentManagement, final UiProperties uiProperties, + final DistributionGridLayoutUiState distributionGridLayoutUiState, final TargetGridLayoutUiState targetGridLayoutUiState, final TagFilterLayoutUiState distributionTagLayoutUiState) { super(uiDependencies, distributionSetManagement, distributionGridLayoutUiState, EventView.DEPLOYMENT); @@ -129,8 +138,10 @@ public class DistributionGrid extends AbstractDsGrid { DsManagementFilterParams::new, getSelectionSupport()::deselectAll)); initFilterMappings(); getFilterSupport().setFilter(new DsManagementFilterParams()); + this.invalidateDistributionSetSupport = new InvalidateDistributionSetSupport(this, i18n, notification, + dsInvalidationManagement); - initTargetPinningStyleGenerator(); + initStyleGenerator(); init(); } @@ -179,8 +190,24 @@ public class DistributionGrid extends AbstractDsGrid { return installedDsId != null ? Collections.singletonList(installedDsId) : Collections.emptyList(); } - private void initTargetPinningStyleGenerator() { - setStyleGenerator(ds -> pinSupport.getAssignedOrInstalledRowStyle(ds.getId())); + private void initStyleGenerator() { + setStyleGenerator(this::getRowStyle); + } + + private String getRowStyle(final ProxyDistributionSet distributionSet) { + final StringBuilder style = new StringBuilder(); + + final String assignedInstalledStyle = pinSupport.getAssignedOrInstalledRowStyle(distributionSet.getId()); + if (assignedInstalledStyle != null) { + style.append(assignedInstalledStyle); + } + + if (!distributionSet.getIsValid()) { + style.append(" "); + style.append(SPUIDefinitions.INVALID_DISTRIBUTION); + } + + return style.toString(); } @Override @@ -194,7 +221,7 @@ public class DistributionGrid extends AbstractDsGrid { addVersionColumn(); GridComponentBuilder.joinToActionColumn(i18n, getDefaultHeaderRow(), - Arrays.asList(addPinColumn(), addDeleteColumn())); + Arrays.asList(addPinColumn(), addDeleteColumn(), addInvalidateColumn())); } private Column addPinColumn() { @@ -206,6 +233,16 @@ public class DistributionGrid extends AbstractDsGrid { pinSupport::getPinningStyle); } + + private Column addInvalidateColumn() { + final ValueProvider buttonProvider = ds -> GridComponentBuilder.buildActionButton( + i18n, clickEvent -> invalidateDistributionSetSupport.openConsequencesWindowOnInvalidateAction(ds), + VaadinIcons.BAN, UIMessageIdProvider.TOOLTIP_INVALIDATE_DISTRIBUTIONSET, + SPUIStyleDefinitions.STATUS_ICON_NEUTRAL, UIComponentIdProvider.DIST_INVALIDATE_ICON + "." + ds.getId(), + ds.getIsValid()); + return GridComponentBuilder.addIconColumn(this, buttonProvider, DS_INVALIDATE_BUTTON_ID, null); + } + @Override public void restoreState() { final Long pinnedDsId = distributionGridLayoutUiState.getPinnedDsId(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionGridLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionGridLayout.java index 8f5b884a7..480d77200 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionGridLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionGridLayout.java @@ -13,6 +13,7 @@ import java.util.Collections; import java.util.List; import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; @@ -66,6 +67,8 @@ public class DistributionGridLayout extends AbstractDistributionSetGridLayout { * TargetManagement * @param distributionSetManagement * DistributionSetManagement + * @param dsInvalidationManagement + * {@link DistributionSetInvalidationManagement} * @param smManagement * SoftwareModuleManagement * @param distributionSetTypeManagement @@ -90,12 +93,15 @@ public class DistributionGridLayout extends AbstractDistributionSetGridLayout { * TargetGridLayoutUiState */ public DistributionGridLayout(final CommonUiDependencies uiDependencies, final TargetManagement targetManagement, - final DistributionSetManagement distributionSetManagement, final SoftwareModuleManagement smManagement, + final DistributionSetManagement distributionSetManagement, + final DistributionSetInvalidationManagement dsInvalidationManagement, + final SoftwareModuleManagement smManagement, final DistributionSetTypeManagement distributionSetTypeManagement, - final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement, - final DeploymentManagement deploymentManagement, final TenantConfigurationManagement configManagement, - final SystemSecurityContext systemSecurityContext, final UiProperties uiProperties, - final DistributionGridLayoutUiState distributionGridLayoutUiState, + final DistributionSetTagManagement distributionSetTagManagement, + + final SystemManagement systemManagement, final DeploymentManagement deploymentManagement, + final TenantConfigurationManagement configManagement, final SystemSecurityContext systemSecurityContext, + final UiProperties uiProperties, final DistributionGridLayoutUiState distributionGridLayoutUiState, final TagFilterLayoutUiState distributionTagLayoutUiState, final TargetGridLayoutUiState targetGridLayoutUiState) { super(uiDependencies, systemManagement, systemSecurityContext, configManagement, distributionSetManagement, @@ -106,8 +112,8 @@ public class DistributionGridLayout extends AbstractDistributionSetGridLayout { this.distributionGridHeader.buildHeader(); this.distributionGrid = new DistributionGrid(uiDependencies, targetManagement, distributionSetManagement, - deploymentManagement, uiProperties, distributionGridLayoutUiState, targetGridLayoutUiState, - distributionTagLayoutUiState); + dsInvalidationManagement, deploymentManagement, uiProperties, distributionGridLayoutUiState, + targetGridLayoutUiState, distributionTagLayoutUiState); this.distributionSetDetailsHeader = new DistributionSetDetailsHeader(uiDependencies, getDsWindowBuilder(), getDsMetaDataWindowBuilder()); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/InvalidateDistributionSetSupport.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/InvalidateDistributionSetSupport.java new file mode 100644 index 000000000..bfa7a8d24 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/InvalidateDistributionSetSupport.java @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.management.dstable; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement; +import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation; +import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType; +import org.eclipse.hawkbit.repository.model.DistributionSetInvalidationCount; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyDistributionSet; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; +import org.eclipse.hawkbit.ui.utils.UINotification; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import com.google.common.collect.Lists; +import com.vaadin.server.Sizeable; +import com.vaadin.ui.UI; + +/** + * Support for invalidate a distribution set in the ds grid. + */ +public class InvalidateDistributionSetSupport { + private static final Logger LOG = LoggerFactory.getLogger(InvalidateDistributionSetSupport.class); + + private final VaadinMessageSource i18n; + private final UINotification notification; + private final DistributionGrid grid; + + private final DistributionSetInvalidationManagement dsInvalidationManagement; + + private InvalidateDsConsequencesDialog consequencesDialog; + + /** + * Constructor for InvalidateDistributionSetSupport + * + * @param grid + * Vaadin Grid + * @param i18n + * VaadinMessageSource + * @param notification + * UINotification + * @param dsInvalidationManagement + * {@link DistributionSetInvalidationManagement} + */ + public InvalidateDistributionSetSupport(final DistributionGrid grid, final VaadinMessageSource i18n, + final UINotification notification, final DistributionSetInvalidationManagement dsInvalidationManagement) { + this.grid = grid; + this.i18n = i18n; + this.notification = notification; + this.dsInvalidationManagement = dsInvalidationManagement; + } + + /** + * Open confirmation pop up window for invalidate distribution set + * + * @param clickedDistributionSet + * {@link ProxyDistributionSet} that should be invalidated + */ + public void openConsequencesWindowOnInvalidateAction(final ProxyDistributionSet clickedDistributionSet) { + final List allDistributionSetsForInvalidation = getDistributionSetsForInvalidation( + clickedDistributionSet); + + consequencesDialog = new InvalidateDsConsequencesDialog(allDistributionSetsForInvalidation, i18n, ok -> { + if (ok) { + openAffectedEntitiesWindowOnInvalidateAction(allDistributionSetsForInvalidation); + } + }); + consequencesDialog.getWindow().setWidth(40.0F, Sizeable.Unit.PERCENTAGE); + + UI.getCurrent().addWindow(consequencesDialog.getWindow()); + consequencesDialog.getWindow().bringToFront(); + } + + private void openAffectedEntitiesWindowOnInvalidateAction( + final List allDistributionSetsForInvalidation) { + + final DistributionSetInvalidationCount entitiesForInvalidationCount = dsInvalidationManagement + .countEntitiesForInvalidation( + getDistributionSetInvalidation(consequencesDialog.isStopRolloutsSelected(), + getDistributionSetIds(allDistributionSetsForInvalidation), CancelationType.NONE)); + + final InvalidateDsAffectedEntitiesDialog affectedEntitiesDialog = new InvalidateDsAffectedEntitiesDialog( + allDistributionSetsForInvalidation, i18n, ok -> { + if (ok) { + handleOkForInvalidateDistributionSet(allDistributionSetsForInvalidation); + } + }, entitiesForInvalidationCount.getRolloutsCount(), + entitiesForInvalidationCount.getAutoAssignmentCount()); + affectedEntitiesDialog.getWindow().setWidth(40.0F, Sizeable.Unit.PERCENTAGE); + + UI.getCurrent().addWindow(affectedEntitiesDialog.getWindow()); + affectedEntitiesDialog.getWindow().bringToFront(); + } + + private void handleOkForInvalidateDistributionSet( + final List allDistributionSetsForInvalidation) { + + try { + dsInvalidationManagement.invalidateDistributionSet( + getDistributionSetInvalidation(consequencesDialog.isStopRolloutsSelected(), + getDistributionSetIds(allDistributionSetsForInvalidation), CancelationType.NONE)); + notification.displaySuccess(createSuccessNotificationText(allDistributionSetsForInvalidation)); + grid.refreshAll(); + } catch (final RuntimeException ex) { + LOG.warn("Invalidating DistributionSets '{}' failed: {}", StringUtils.collectionToCommaDelimitedString( + getDistributionSetIds(allDistributionSetsForInvalidation)), ex.getMessage()); + notification.displayWarning(createFailureNotificationText(allDistributionSetsForInvalidation)); + throw ex; + } + } + + private DistributionSetInvalidation getDistributionSetInvalidation(final boolean stopRollouts, + final List distSetIds, final CancelationType cancelationType) { + return new DistributionSetInvalidation(distSetIds, cancelationType, stopRollouts); + } + + private String createFailureNotificationText(final List allDistributionSetsForInvalidation) { + String failureNotificationText = ""; + if (allDistributionSetsForInvalidation.size() == 1) { + failureNotificationText = i18n.getMessage( + UIMessageIdProvider.MESSAGE_INVALIDATE_DISTRIBUTIONSET_FAIL_SINGULAR, + allDistributionSetsForInvalidation.get(0).getNameVersion()); + } else { + failureNotificationText = i18n.getMessage( + UIMessageIdProvider.MESSAGE_INVALIDATE_DISTRIBUTIONSET_FAIL_PLURAL, + allDistributionSetsForInvalidation.size()); + } + return failureNotificationText; + } + + private String createSuccessNotificationText(final List allDistributionSetsForInvalidation) { + String successNotificationText = ""; + if (allDistributionSetsForInvalidation.size() == 1) { + successNotificationText = i18n.getMessage( + UIMessageIdProvider.MESSAGE_INVALIDATE_DISTRIBUTIONSET_SUCCESS_SINGULAR, + allDistributionSetsForInvalidation.get(0).getNameVersion()); + } else { + successNotificationText = i18n.getMessage( + UIMessageIdProvider.MESSAGE_INVALIDATE_DISTRIBUTIONSET_SUCCESS_PLURAL, + allDistributionSetsForInvalidation.size()); + } + return successNotificationText; + } + + private static List getDistributionSetIds( + final List allDistributionSetsForInvalidation) { + return allDistributionSetsForInvalidation.stream().map(ProxyDistributionSet::getId) + .collect(Collectors.toList()); + } + + private List getDistributionSetsForInvalidation(final ProxyDistributionSet clickedItem) { + final List selectedItems = Lists.newArrayList(grid.getSelectedItems()); + + if (selectedItems.contains(clickedItem)) { + // consider only valid DS for invalidation + return selectedItems.stream().filter(ProxyDistributionSet::getIsValid).collect(Collectors.toList()); + } else { + // only clicked item should be invalidated if it is not part of the + // selection + grid.deselectAll(); + grid.select(clickedItem); + + return Collections.singletonList(clickedItem); + } + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/InvalidateDsAffectedEntitiesDialog.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/InvalidateDsAffectedEntitiesDialog.java new file mode 100644 index 000000000..768c7cbb1 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/InvalidateDsAffectedEntitiesDialog.java @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.management.dstable; + +import java.util.List; +import java.util.function.Consumer; + +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.ui.common.CommonDialogWindow; +import org.eclipse.hawkbit.ui.common.CommonDialogWindow.ConfirmStyle; +import org.eclipse.hawkbit.ui.common.CommonDialogWindow.SaveDialogCloseListener; +import org.eclipse.hawkbit.ui.common.builder.WindowBuilder; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyDistributionSet; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleTiny; +import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; +import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; + +import com.vaadin.ui.Label; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; + +/** + * Showing the affected entities for invalidating a distribution set. + */ +public class InvalidateDsAffectedEntitiesDialog { + + private final Consumer callback; + + private final CommonDialogWindow window; + + private final VaadinMessageSource i18n; + + /** + * Constructor for {@link InvalidateDsAffectedEntitiesDialog} + * + * @param allDistributionSetsForInvalidation + * {@link List} of {@link ProxyDistributionSet} that are selected + * for invalidation + * @param i18n + * {@link VaadinMessageSource} + * @param callback + * callback for dialog result + * @param affectedRollouts + * number of affected {@link Rollout}s + * @param affectedAutoAssignments + * number of affected auto assignments + */ + public InvalidateDsAffectedEntitiesDialog(final List allDistributionSetsForInvalidation, + final VaadinMessageSource i18n, final Consumer callback, final long affectedRollouts, + final long affectedAutoAssignments) { + + this.i18n = i18n; + + final VerticalLayout content = new VerticalLayout(); + content.setSpacing(true); + content.setMargin(true); + + final Label consequencesLabel = new Label(createConsequencesText(allDistributionSetsForInvalidation)); + consequencesLabel.setWidthFull(); + content.addComponent(consequencesLabel); + + final Label stoppedRolloutsLabel = new Label(i18n.getMessage( + UIMessageIdProvider.MESSAGE_INVALIDATE_DISTRIBUTIONSET_AFFECTED_ENTITIES_ROLLOUTS, affectedRollouts)); + stoppedRolloutsLabel.setId(UIComponentIdProvider.INVALIDATE_DS_AFFECTED_ENTITIES_ROLLOUTS); + content.addComponent(stoppedRolloutsLabel); + + final Label stoppedAutoAssignmentsLabel = new Label(i18n.getMessage( + UIMessageIdProvider.MESSAGE_INVALIDATE_DISTRIBUTIONSET_AFFECTED_ENTITIES_AUTOASSIGNMENTS, + affectedAutoAssignments)); + stoppedAutoAssignmentsLabel.setId(UIComponentIdProvider.INVALIDATE_DS_AFFECTED_ENTITIES_AUTOASSIGNMENTS); + content.addComponent(stoppedAutoAssignmentsLabel); + + final WindowBuilder windowBuilder = new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW) + .id(UIComponentIdProvider.INVALIDATE_DS_AFFECTED_ENTITIES) + .caption(i18n.getMessage(UIMessageIdProvider.CAPTION_INVALIDATE_DISTRIBUTIONSET_AFFECTED_ENTITIES)) + .content(content).cancelButtonClickListener(e -> callback.accept(false)) + .saveDialogCloseListener(getSaveDialogCloseListener()).hideMandatoryExplanation() + .buttonDecorator(SPUIButtonStyleTiny.class).confirmStyle(ConfirmStyle.CONFIRM).i18n(i18n); + + this.window = windowBuilder.buildCommonDialogWindow(); + window.setSaveButtonEnabled(true); + window.addStyleName(SPUIStyleDefinitions.CONFIRMBOX_WINDOW_STYLE); + this.callback = callback; + + } + + private String createConsequencesText(final List allDistributionSetsForInvalidation) { + String consequencesText = ""; + if (allDistributionSetsForInvalidation.size() == 1) { + final ProxyDistributionSet distributionSet = allDistributionSetsForInvalidation.get(0); + consequencesText = i18n.getMessage( + UIMessageIdProvider.MESSAGE_INVALIDATE_DISTRIBUTIONSET_AFFECTED_ENTITIES_INTRO_SINGULAR, + HawkbitCommonUtil.getFormattedNameVersion(distributionSet.getName(), distributionSet.getVersion())); + } else { + consequencesText = i18n.getMessage( + UIMessageIdProvider.MESSAGE_INVALIDATE_DISTRIBUTIONSET_AFFECTED_ENTITIES_INTRO_PLURAL, + allDistributionSetsForInvalidation.size()); + } + + return consequencesText; + } + + private SaveDialogCloseListener getSaveDialogCloseListener() { + return new SaveDialogCloseListener() { + @Override + public void saveOrUpdate() { + callback.accept(true); + } + + @Override + public boolean canWindowSaveOrUpdate() { + return true; + } + }; + } + + /** + * @return confirmation window + */ + public Window getWindow() { + return window; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/InvalidateDsConsequencesDialog.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/InvalidateDsConsequencesDialog.java new file mode 100644 index 000000000..a332fd6af --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/InvalidateDsConsequencesDialog.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.management.dstable; + +import java.util.List; +import java.util.function.Consumer; + +import org.eclipse.hawkbit.ui.common.CommonDialogWindow; +import org.eclipse.hawkbit.ui.common.CommonDialogWindow.ConfirmStyle; +import org.eclipse.hawkbit.ui.common.CommonDialogWindow.SaveDialogCloseListener; +import org.eclipse.hawkbit.ui.common.builder.WindowBuilder; +import org.eclipse.hawkbit.ui.common.data.proxies.ProxyDistributionSet; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleTiny; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; +import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; + +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.Label; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; + +/** + * Showing the consequences for invalidating distribution set. + */ +public class InvalidateDsConsequencesDialog { + + private final Consumer callback; + + private final CommonDialogWindow window; + + private final CheckBox stopRolloutsCheckBox; + + private final VaadinMessageSource i18n; + + /** + * Constructor for {@link InvalidateDsConsequencesDialog} + * + * @param allDistributionSetsForInvalidation + * {@link List} of {@link ProxyDistributionSet} that are selected + * for invalidation + * @param i18n + * {@link VaadinMessageSource} + * @param callback + * callback for dialog result + */ + public InvalidateDsConsequencesDialog(final List allDistributionSetsForInvalidation, + final VaadinMessageSource i18n, final Consumer callback) { + + this.i18n = i18n; + final VerticalLayout content = new VerticalLayout(); + content.setSpacing(true); + content.setMargin(true); + + final Label consequencesLabel = new Label(createConsequencesText(allDistributionSetsForInvalidation)); + consequencesLabel.setWidthFull(); + content.addComponent(consequencesLabel); + + stopRolloutsCheckBox = new CheckBox(); + stopRolloutsCheckBox.setId(UIComponentIdProvider.INVALIDATE_DS_STOP_ROLLOUTS); + stopRolloutsCheckBox.setCaption(i18n.getMessage(UIMessageIdProvider.LABEL_INVALIDATE_DS_STOP_ROLLOUTS)); + content.addComponent(stopRolloutsCheckBox); + + final WindowBuilder windowBuilder = new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW) + .id(UIComponentIdProvider.INVALIDATE_DS_CONSEQUENCES) + .caption(createCaption(allDistributionSetsForInvalidation)).content(content) + .cancelButtonClickListener(e -> callback.accept(false)) + .saveDialogCloseListener(getSaveDialogCloseListener()).hideMandatoryExplanation() + .buttonDecorator(SPUIButtonStyleTiny.class).confirmStyle(ConfirmStyle.NEXT).i18n(i18n); + + this.window = windowBuilder.buildCommonDialogWindow(); + window.setSaveButtonEnabled(true); + window.addStyleName(SPUIStyleDefinitions.CONFIRMBOX_WINDOW_STYLE); + this.callback = callback; + + } + + private SaveDialogCloseListener getSaveDialogCloseListener() { + return new SaveDialogCloseListener() { + @Override + public void saveOrUpdate() { + callback.accept(true); + } + + @Override + public boolean canWindowSaveOrUpdate() { + return true; + } + }; + } + + private String createCaption(final List allDistributionSetsForInvalidation) { + String caption = ""; + if (allDistributionSetsForInvalidation.size() == 1) { + caption = i18n.getMessage(UIMessageIdProvider.CAPTION_INVALIDATE_DISTRIBUTIONSET_CONSEQUENCES_SINGULAR, + allDistributionSetsForInvalidation.get(0).getNameVersion()); + } else { + caption = i18n.getMessage(UIMessageIdProvider.CAPTION_INVALIDATE_DISTRIBUTIONSET_CONSEQUENCES_PLURAL, + allDistributionSetsForInvalidation.size()); + } + return caption; + } + + private String createConsequencesText(final List allDistributionSetsForInvalidation) { + if (allDistributionSetsForInvalidation.size() == 1) { + return i18n.getMessage(UIMessageIdProvider.MESSAGE_INVALIDATE_DISTRIBUTIONSET_CONSEQUENCES_SINGULAR); + } else { + return i18n.getMessage(UIMessageIdProvider.MESSAGE_INVALIDATE_DISTRIBUTIONSET_CONSEQUENCES_PLURAL, + allDistributionSetsForInvalidation.size()); + } + } + + /** + * Returns the user selection stop rollouts + * + * @return boolean value of checkbox stop rollouts + */ + boolean isStopRolloutsSelected() { + return stopRolloutsCheckBox.getValue(); + } + + /** + * @return confirmation window + */ + public Window getWindow() { + return window; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutGrid.java index 11e3dc970..b2c9d62d8 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutGrid.java @@ -49,6 +49,7 @@ import org.eclipse.hawkbit.ui.common.grid.support.SelectionSupport; import org.eclipse.hawkbit.ui.rollout.DistributionBarHelper; import org.eclipse.hawkbit.ui.rollout.RolloutManagementUIState; import org.eclipse.hawkbit.ui.rollout.window.RolloutWindowBuilder; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; @@ -137,6 +138,7 @@ public class RolloutGrid extends AbstractGrid { UIComponentIdProvider.ROLLOUT_STATUS_LABEL_ID); actionTypeIconSupplier = new ActionTypeIconSupplier<>(i18n, ProxyRollout::getActionType, UIComponentIdProvider.ROLLOUT_ACTION_TYPE_LABEL_ID); + init(); } @@ -266,8 +268,9 @@ public class RolloutGrid extends AbstractGrid { GridComponentBuilder.addDescriptionColumn(this, i18n, DESC_ID).setHidable(true).setHidden(true); - GridComponentBuilder.addColumn(this, ProxyRollout::getDsNameVersion).setId(DIST_NAME_VERSION_ID) - .setCaption(i18n.getMessage("header.distributionset")).setHidable(true).setExpandRatio(2); + GridComponentBuilder.addColumn(this, ProxyRollout::getDsNameVersion, this::getDistributionCellStyle) + .setId(DIST_NAME_VERSION_ID).setCaption(i18n.getMessage("header.distributionset")) + .setDescriptionGenerator(this::createDSTooltipText).setHidable(true).setExpandRatio(2); GridComponentBuilder .addIconColumn(this, rolloutStatusIconSupplier::getLabel, STATUS_ID, i18n.getMessage("header.status")) @@ -464,4 +467,20 @@ public class RolloutGrid extends AbstractGrid { onSelectedRolloutDeleted(selectedRolloutId); } } + + private String getDistributionCellStyle(final ProxyRollout rollout) { + if (!rollout.getDsInfo().isValid()) { + return SPUIDefinitions.INVALID_DISTRIBUTION; + } + return null; + } + + protected String createDSTooltipText(final ProxyRollout rollout) { + final StringBuilder tooltipText = new StringBuilder(rollout.getDsInfo().getNameVersion()); + if (!rollout.getDsInfo().isValid()) { + tooltipText.append(" - "); + tooltipText.append(i18n.getMessage(UIMessageIdProvider.TOOLTIP_DISTRIBUTIONSET_INVALID)); + } + return tooltipText.toString(); + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/controllers/CopyRolloutWindowController.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/controllers/CopyRolloutWindowController.java index ba44eda60..4b4ce3af5 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/controllers/CopyRolloutWindowController.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/controllers/CopyRolloutWindowController.java @@ -61,6 +61,7 @@ public class CopyRolloutWindowController extends AddRolloutWindowController { proxyRolloutWindow.setName(getI18n().getMessage("textfield.rollout.copied.name", proxyRolloutWindow.getName())); + removeDistributionSetIfInvalid(proxyRolloutWindow); setTargetFilterId(proxyRolloutWindow); if (proxyRolloutWindow.getForcedTime() == null @@ -84,6 +85,14 @@ public class CopyRolloutWindowController extends AddRolloutWindowController { return proxyRolloutWindow; } + private void removeDistributionSetIfInvalid(final ProxyRolloutWindow proxyRolloutWindow) { + final boolean dsIsValid = proxyRolloutWindow.getRolloutForm().getDistributionSetInfo().isValid(); + if (!dsIsValid) { + proxyRolloutWindow.getRolloutForm().setDistributionSetInfo(null); + } + + } + private void setTargetFilterId(final ProxyRolloutWindow proxyRolloutWindow) { final Page filterQueries = targetFilterQueryManagement.findByQuery(PageRequest.of(0, 1), proxyRolloutWindow.getTargetFilterQuery()); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java index bb6d8b265..4aab5dffa 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java @@ -203,7 +203,12 @@ public final class SPUIDefinitions { public static final String FILTER_RESET_ICON = "filter-reset-icon"; /* Action History */ - public static final String DISABLE_DISTRIBUTION = "incomplete-distribution"; + public static final String INCOMPLETE_DISTRIBUTION = "incomplete-distribution"; + + /** + * marker for invalid distribution sets + */ + public static final String INVALID_DISTRIBUTION = "invalid-distribution"; /** * Filter by type layout width. diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java index 778c7244c..56e7ce656 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java @@ -52,9 +52,13 @@ public final class UIComponentIdProvider { */ public static final String TARGET_PIN_ICON = "target.pin.icon"; /** - * ID-Targ.PIN. + * ID-DistributionSet pin icon. */ public static final String DIST_PIN_ICON = "dist.pin.icon"; + /** + * ID-DistributionSet invalidate icon. + */ + public static final String DIST_INVALIDATE_ICON = "dist.invalidate.icon"; /** * ID-Targ.DELETE. */ @@ -1360,6 +1364,28 @@ public final class UIComponentIdProvider { public static final String DIST_SET_SELECT_CONS_WINDOW_ID = "distribution.set.select.consequences.window"; public static final String DIST_SET_SELECT_ENABLE_ID = "distribution.set.select.enable"; + + /** + * Distribution set invalidate consequences window id + */ + public static final String INVALIDATE_DS_CONSEQUENCES = "invalidate.distributionset.consequences.window"; + /** + * Distribution set invalidate affected entities window id + */ + public static final String INVALIDATE_DS_AFFECTED_ENTITIES = "invalidate.distributionset.affectedentities.window"; + /** + * Distribution set invalidate affected rollouts label id + */ + public static final String INVALIDATE_DS_AFFECTED_ENTITIES_ROLLOUTS = "invalidate.distributionset.affectedentities.rollouts"; + /** + * Distribution set invalidate affected autoassignments label id + */ + public static final String INVALIDATE_DS_AFFECTED_ENTITIES_AUTOASSIGNMENTS = "invalidate.distributionset.affectedentities.autoassignments"; + /** + * Distribution set invalidate consequences window, stop rollouts checkbox + */ + public static final String INVALIDATE_DS_STOP_ROLLOUTS = "invalidate.distributionset.consequences.stop.rollouts.checkbox"; + /** * Id of the unread notification button */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java index 1028aa6c9..18f19ee63 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java @@ -25,6 +25,10 @@ public final class UIMessageIdProvider { public static final String BUTTON_SAVE = "button.save"; + public static final String BUTTON_NEXT = "button.next"; + + public static final String BUTTON_CONFIRM = "button.confirm"; + public static final String BUTTON_NO_AUTO_ASSIGNMENT = "button.no.auto.assignment"; public static final String BUTTON_AUTO_ASSIGNMENT_DESCRIPTION = "button.auto.assignment.desc"; @@ -71,6 +75,12 @@ public final class UIMessageIdProvider { public static final String CAPTION_ENTITY_ASSIGN_ACTION_CONFIRMBOX = "caption.entity.assign.action.confirmbox"; + public static final String CAPTION_INVALIDATE_DISTRIBUTIONSET_CONSEQUENCES_SINGULAR = "caption.invalidate.distributionset.consequences.singular"; + + public static final String CAPTION_INVALIDATE_DISTRIBUTIONSET_CONSEQUENCES_PLURAL = "caption.invalidate.distributionset.consequences.plural"; + + public static final String CAPTION_INVALIDATE_DISTRIBUTIONSET_AFFECTED_ENTITIES = "caption.invalidate.distributionset.affected.entities"; + public static final String CAPTION_CONFIG_CREATE = "caption.config.create"; public static final String CAPTION_CONFIG_EDIT = "caption.config.edit"; @@ -93,6 +103,8 @@ public final class UIMessageIdProvider { public static final String LABEL_AUTO_ASSIGNMENT_ENABLE = "label.auto.assign.enable"; + public static final String LABEL_INVALIDATE_DS_STOP_ROLLOUTS = "label.invalidate.distributionset.stop.rollouts"; + public static final String MESSAGE_NO_DATA = "message.no.data"; public static final String MESSAGE_DATA_AVAILABLE = "message.data.available"; @@ -111,12 +123,18 @@ public final class UIMessageIdProvider { public static final String MESSAGE_AUTOASSIGN_CREATE_ERROR_MISSINGELEMENTS = "message.autoassign.create.error.missingElements"; + public static final String MESSAGE_DISTRIBUTION_ASSIGNED = "message.dist.already.assigned"; + + public static final String MESSAGE_DISTRIBUTION_NOT_ASSIGNED = "message.dist.not.assigned"; + public static final String MESSAGE_ERROR_NAMEREQUIRED = "message.error.nameRequired"; public static final String MESSAGE_ERROR_VERSIONREQUIRED = "message.error.versionRequired"; public static final String MESSAGE_ERROR_DISTRIBUTIONSET_REQUIRED = "message.error.distributionSetRequired"; + public static final String MESSAGE_ERROR_DISTRIBUTIONSET_INVALID = "message.error.distributionset.invalid"; + public static final String MESSAGE_ERROR_TFQ_REQUIRED = "message.error.tfqRequired"; public static final String MESSAGE_FILTER_QUERY_ERROR_NOTVALIDE = "message.filter.query.error.notValide"; @@ -171,6 +189,12 @@ public final class UIMessageIdProvider { public static final String TOOLTIP_DISTRIBUTION_SET_PIN = "tooltip.distribution.set.pin"; + public static final String TOOLTIP_INVALIDATE_DISTRIBUTIONSET = "tooltip.invalidate.distributionset"; + + public static final String TOOLTIP_DISTRIBUTIONSET_INVALID = "tooltip.distributionset.invalid"; + + public static final String TOOLTIP_DISTRIBUTIONSET_INCOMPLETE = "tooltip.distributionset.incomplete"; + public static final String TOOLTIP_TIMEFORCED_ITEM = "tooltip.timeforced.item"; public static final String TOOLTIP_TIMEFORCED_FORCED_IN = "tooltip.timeforced.forced.in"; @@ -233,12 +257,36 @@ public final class UIMessageIdProvider { public static final String CAPTION_SOFTWARE_MODULE = "caption.software.module"; + public static final String CAPTION_ERROR = "caption.error"; + + public static final String MESSAGE_ERROR = "message.error"; + public static final String MESSAGE_TARGET_BULKUPLOAD_RESULT_SUCCESS = "message.bulk.upload.result.success"; public static final String MESSAGE_TARGET_BULKUPLOAD_RESULT_FAIL = "message.bulk.upload.result.fail"; public static final String MESSAGE_CLEAR_FILE_UPLOAD_QUEUE = "message.clear.file.upload.queue.confirm"; + public static final String MESSAGE_INVALIDATE_DISTRIBUTIONSET_CONSEQUENCES_SINGULAR = "message.invalidate.distributionset.consequences.singular"; + + public static final String MESSAGE_INVALIDATE_DISTRIBUTIONSET_CONSEQUENCES_PLURAL = "message.invalidate.distributionset.consequences.plural"; + + public static final String MESSAGE_INVALIDATE_DISTRIBUTIONSET_AFFECTED_ENTITIES_INTRO_SINGULAR = "message.invalidate.distributionset.affected.entities.intro.singular"; + + public static final String MESSAGE_INVALIDATE_DISTRIBUTIONSET_AFFECTED_ENTITIES_INTRO_PLURAL = "message.invalidate.distributionset.affected.entities.intro.plural"; + + public static final String MESSAGE_INVALIDATE_DISTRIBUTIONSET_AFFECTED_ENTITIES_ROLLOUTS = "message.invalidate.distributionset.affected.entities.rollouts"; + + public static final String MESSAGE_INVALIDATE_DISTRIBUTIONSET_AFFECTED_ENTITIES_AUTOASSIGNMENTS = "message.invalidate.distributionset.affected.entities.autoassignments"; + + public static final String MESSAGE_INVALIDATE_DISTRIBUTIONSET_SUCCESS_SINGULAR = "message.invalidate.distributionset.success.singular"; + + public static final String MESSAGE_INVALIDATE_DISTRIBUTIONSET_SUCCESS_PLURAL = "message.invalidate.distributionset.success.plural"; + + public static final String MESSAGE_INVALIDATE_DISTRIBUTIONSET_FAIL_SINGULAR = "message.invalidate.distributionset.fail.singular"; + + public static final String MESSAGE_INVALIDATE_DISTRIBUTIONSET_FAIL_PLURAL = "message.invalidate.distributionset.fail.plural"; + public static final String VAADIN_SYSTEM_SESSIONEXPIRED_CAPTION = "vaadin.system.sessionexpired.caption"; public static final String VAADIN_SYSTEM_SESSIONEXPIRED_MESSAGE = "vaadin.system.sessionexpired.message"; diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss index f7b468b71..abce5c2bf 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss @@ -201,6 +201,14 @@ .v-grid-row.incomplete-distribution>.v-grid-cell { color: $disabled-row-color-grey !important; } + + // Disabled row/cell style when distribution is invalid + .v-grid-row.invalid-distribution, + .v-grid-row.invalid-distribution>.v-grid-cell, + td.v-grid-cell.invalid-distribution { + color: $disabled-row-color-grey !important; + text-decoration: line-through !important; + } .v-link { text-decoration: none; diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index 0eedba716..592b0792c 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -34,6 +34,8 @@ button.actions = You have actions button.no.actions = No actions button.ok = OK button.cancel = Cancel +button.next = Next +button.confirm = Confirm button.upload.file = Upload File button.no.auto.assignment = none button.auto.assignment.desc = Select auto assign distribution set @@ -154,6 +156,9 @@ caption.config.edit = edit caption.config.delete = delete caption.metadata.popup = Metadata of +caption.invalidate.distributionset.consequences.singular = Invalidate distribution set - {0} +caption.invalidate.distributionset.consequences.plural = Invalidate {0} distribution sets +caption.invalidate.distributionset.affected.entities = Invalidation confirmation caption.confirm.assign.consequences = Auto assign consequences caption.auto.assignment.ds = Auto assignment @@ -249,6 +254,7 @@ label.approval.remark = Remark (optional) label.drop.area.upload = Drop Files to upload label.errorthreshold.option.percent = % label.errorthreshold.option.count = Count +label.invalidate.distributionset.stop.rollouts = Stop rollouts # TextFields prefix with - textfield textfield.name = Name @@ -369,6 +375,9 @@ tooltip.target.status.pending = Status pending tooltip.target.status.registered = Status registered tooltip.target.pin = Pin target tooltip.distribution.set.pin = Pin distribution set +tooltip.invalidate.distributionset = Invalidate distribution set.. +tooltip.distributionset.invalid = invalid +tooltip.distributionset.incomplete = incomplete tooltip.in.time = In Time # Notification messages prefix with - message @@ -463,6 +472,7 @@ message.error.missing.filtername = Please select target filter name message.error.missing.controllerId = Missing Controller Id message.error.missing.nameorversion = Missing Name or Version message.error.missing.nameorversionortype = Missing Name or Version or Type +message.error.distributionset.invalid = Action not allowed for invalid distribution set {0} message.type.delete = Please unclick the type {0}, then try to delete message.error.dist.set.type.update= Distribution set type is already assigned to set(s) and cannot be changed message.no.directory.upload = Directory upload is not supported @@ -478,7 +488,7 @@ message.bulk.upload.upload.started = Uploading a file.. message.bulk.upload.provisioning.started = Provisioning targets.. message.bulk.upload.assignment.started = Assigning tags and DS.. message.bulk.upload.tag.assignment.failed = Tag {0} assignment failed as tag no longer exists -message.bulk.upload.tag.assignments.failed= Few tag assignments failed as tags no longer exists +message.bulk.upload.tag.assignments.failed= Few tag assignments failed as tags no longer exists message.confirm.assign.consequences.none = This auto assignment will not have any effect on the currently available targets. In future added targets might match the filter and will receive the selected distribution set automatically. message.confirm.assign.consequences.text = When you confirm this auto assignment, {0} targets which match the filter will immediately get assigned with the selected distribution set. message.maintenancewindow.schedule.required.error = Please provide a Cron expression @@ -501,6 +511,16 @@ message.forcedTime.cannotBeEmpty = Forced time can not be empty message.forcedTime.missing = Force time is not specified for the time-forced assignment message.scheduledTime.cannotBeEmpty = Scheduled time can not be empty message.clear.file.upload.queue.confirm = There is still at least one queued file upload pending. By leaving this view the upload queue will be cleared. \nAre you sure you want to leave this view? +message.invalidate.distributionset.success.singular = Distribution set {0} invalidated successfully +message.invalidate.distributionset.success.plural = {0} distribution sets invalidated successfully +message.invalidate.distributionset.fail.singular = Distribution set {0} invalidation failed +message.invalidate.distributionset.fail.plural = Invalidation failed for {0} distribution sets +message.invalidate.distributionset.consequences.singular = By invalidating this distribution set it can no longer be used in any assignment. All active auto assignments and optionally all active rollouts of the distribution set can be stopped as well as all existing update actions will be canceled. A summary of affected entities will be shown on the next page. +message.invalidate.distributionset.consequences.plural = By invalidating this {0} distribution sets they can no longer be used in any assignment. All active auto assignments and optionally all active rollouts of the distribution sets can be stopped as well as all existing update actions will be canceled. A summary of affected entities will be shown on the next page. +message.invalidate.distributionset.affected.entities.intro.singular = When you confirm the invalidation of distribution set {0} the following actions will be taken: +message.invalidate.distributionset.affected.entities.intro.plural = When you confirm the invalidation of {0} distribution sets the following actions will be taken: +message.invalidate.distributionset.affected.entities.rollouts = {0} rollout(s) will be stopped +message.invalidate.distributionset.affected.entities.autoassignments = {0} auto assignment(s) will be stopped # action info action.target.table.selectall = Select all (Ctrl+A) @@ -728,6 +748,7 @@ message.rollout.noofgroups.or.targetfilter.missing = Please enter number of grou message.rollouts = Rollouts label.target.per.group = Targets per group: message.dist.already.assigned = Distribution {0} is already assigned to target +message.dist.not.assigned = Distribution {0} was not assigned to target message.error.creating.rollout = Server error. Error creating Rollout. Please contact the administrator message.error.starting.rollout = Server error. Error starting Rollout. Please contact the administrator