diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java index a46fbf79a..ce775d2ce 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java @@ -96,7 +96,7 @@ public final class MgmtRestConstants { /** * The tag URL mapping rest resource. */ - public static final String TARGET_TAG_TAGERTS_REQUEST_MAPPING = "/{targetTagId}/targets"; + public static final String TARGET_TAG_TARGETS_REQUEST_MAPPING = "/{targetTagId}/targets"; /** * The tag URL mapping rest resource. diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetTagRestApi.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetTagRestApi.java index 4f59cbab4..35d32c6ae 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetTagRestApi.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetTagRestApi.java @@ -121,7 +121,7 @@ public interface MgmtTargetTagRestApi { * * @return the list of assigned targets. */ - @RequestMapping(method = RequestMethod.GET, value = MgmtRestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING, produces = { + @RequestMapping(method = RequestMethod.GET, value = MgmtRestConstants.TARGET_TAG_TARGETS_REQUEST_MAPPING, produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) ResponseEntity> getAssignedTargets(@PathVariable("targetTagId") final Long targetTagId); @@ -136,7 +136,7 @@ public interface MgmtTargetTagRestApi { * * @return the list of assigned targets and unassigned targets. */ - @RequestMapping(method = RequestMethod.POST, value = MgmtRestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING + @RequestMapping(method = RequestMethod.POST, value = MgmtRestConstants.TARGET_TAG_TARGETS_REQUEST_MAPPING + "/toggleTagAssignment", consumes = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) @@ -154,7 +154,7 @@ public interface MgmtTargetTagRestApi { * * @return the list of assigned targets. */ - @RequestMapping(method = RequestMethod.POST, value = MgmtRestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING, consumes = { + @RequestMapping(method = RequestMethod.POST, value = MgmtRestConstants.TARGET_TAG_TARGETS_REQUEST_MAPPING, consumes = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) ResponseEntity> assignTargets(@PathVariable("targetTagId") final Long targetTagId, @@ -167,7 +167,7 @@ public interface MgmtTargetTagRestApi { * the ID of the target tag to retrieve * @return http status code */ - @RequestMapping(method = RequestMethod.DELETE, value = MgmtRestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING) + @RequestMapping(method = RequestMethod.DELETE, value = MgmtRestConstants.TARGET_TAG_TARGETS_REQUEST_MAPPING) ResponseEntity unassignTargets(@PathVariable("targetTagId") final Long targetTagId); /** @@ -179,7 +179,7 @@ public interface MgmtTargetTagRestApi { * the ID of the target to unassign * @return http status code */ - @RequestMapping(method = RequestMethod.DELETE, value = MgmtRestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING + @RequestMapping(method = RequestMethod.DELETE, value = MgmtRestConstants.TARGET_TAG_TARGETS_REQUEST_MAPPING + "/{controllerId}") ResponseEntity unassignTarget(@PathVariable("targetTagId") final Long targetTagId, @PathVariable("controllerId") final String controllerId); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index 84325efbb..58e79c458 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -62,7 +62,7 @@ public interface DeploymentManagement { * @throws IncompleteDistributionSetException * if mandatory {@link SoftwareModuleType} are not assigned as * define by the {@link DistributionSetType}. - * + * * @throws EntityNotFoundException * if either provided {@link DistributionSet} or {@link Target}s * do not exist @@ -84,7 +84,7 @@ public interface DeploymentManagement { * @throws IncompleteDistributionSetException * if mandatory {@link SoftwareModuleType} are not assigned as * define by the {@link DistributionSetType}. - * + * * @throws EntityNotFoundException * if either provided {@link DistributionSet} or {@link Target}s * do not exist @@ -108,7 +108,7 @@ public interface DeploymentManagement { * @throws IncompleteDistributionSetException * if mandatory {@link SoftwareModuleType} are not assigned as * define by the {@link DistributionSetType}. - * + * * @throws EntityNotFoundException * if either provided {@link DistributionSet} or {@link Target}s * do not exist @@ -145,7 +145,7 @@ public interface DeploymentManagement { * @param controllerId * the target associated to the actions to count * @return the count value of found actions associated to the target - * + * * @throws RSQLParameterUnsupportedFieldException * if a field in the RSQL string is used but not provided by the * given {@code fieldNameProvider} @@ -290,6 +290,19 @@ public interface DeploymentManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) Page findActionStatusByActionWithMessages(@NotNull Pageable pageable, @NotNull Long actionId); + /** + * Retrieves all messages for an {@link ActionStatus}. + * + * + * @param pageable + * the page request parameter for paging and sorting the result + * @param actionStatusId + * the id of {@link ActionStatus} to retrieve the messages from + * @return a page of messages by a specific {@link ActionStatus} id + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Page findMessagesByActionStatusId(@NotNull Pageable pageable, @NotNull Long actionStatusId); + /** * Retrieves all {@link Action}s of a specific target ordered by action ID. * @@ -297,7 +310,7 @@ public interface DeploymentManagement { * the target associated with the actions * @return a list of actions associated with the given target ordered by * action ID - * + * * @throws EntityNotFoundException * if target with given ID does not exist */ @@ -392,7 +405,7 @@ public interface DeploymentManagement { /** * All {@link ActionStatus} entries in the repository. - * + * * @param pageable * the pagination parameter * @return {@link Page} of {@link ActionStatus} entries diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionRepository.java index 324a60533..36c626a69 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionRepository.java @@ -78,6 +78,17 @@ public interface ActionRepository extends BaseEntityRepository, */ Slice findByTargetControllerId(Pageable pageable, String controllerId); + /** + * Retrieves all {@link Action}s which are referring the given targetId + * + * @param pageable + * page parameters + * @param targetId + * the target to find assigned actions for + * @return the found {@link Action}s + */ + Page findByTargetId(Pageable pageable, Long targetId); + /** * Retrieves all {@link Action}s which are active and referring to the given * {@link Target} order by ID ascending. @@ -93,14 +104,14 @@ public interface ActionRepository extends BaseEntityRepository, /** * Retrieves the oldest {@link Action} that is active and referring to the * given {@link Target}. - * + * * @param sort * order * @param controllerId * the target to find assigned actions * @param active * the action active flag - * + * * @return the found {@link Action} */ @EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD) @@ -209,6 +220,15 @@ public interface ActionRepository extends BaseEntityRepository, */ Long countByTargetControllerId(String controllerId); + /** + * Counts all {@link Action}s referring to the given targetId. + * + * @param targetId + * the target to count the {@link Action}s + * @return the count of actions referring to the given target + */ + Long countByTargetId(Long targetId); + /** * Counts all {@link Action}s referring to the given DistributionSet. * diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionStatusRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionStatusRepository.java index 00af2653a..1d48e520f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionStatusRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionStatusRepository.java @@ -40,8 +40,8 @@ public interface ActionStatusRepository /** * Retrieves all {@link ActionStatus} entries from repository of given - * {@link Action}. - * + * ActionId. + * * @param pageReq * parameters * @param actionId @@ -64,5 +64,4 @@ public interface ActionStatusRepository */ @EntityGraph(value = "ActionStatus.withMessages", type = EntityGraphType.LOAD) Page getByActionId(Pageable pageReq, Long actionId); - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java index c7be7aff4..c61d12b8b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java @@ -43,6 +43,7 @@ import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; +import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus_; import org.eclipse.hawkbit.repository.jpa.model.JpaActionWithStatusCount; import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; @@ -656,6 +657,29 @@ public class JpaDeploymentManagement implements DeploymentManagement { return actionStatusRepository.getByActionId(pageReq, actionId); } + @Override + public Page findMessagesByActionStatusId(final Pageable pageable, final Long actionStatusId) { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + + final CriteriaQuery countMsgQuery = cb.createQuery(Long.class); + final Root countMsgQueryFrom = countMsgQuery.distinct(true).from(JpaActionStatus.class); + final ListJoin cJoin = countMsgQueryFrom.joinList("messages", JoinType.LEFT); + countMsgQuery.select(cb.count(cJoin)) + .where(cb.equal(countMsgQueryFrom.get(JpaActionStatus_.id), actionStatusId)); + final Long totalCount = entityManager.createQuery(countMsgQuery).getSingleResult(); + + final CriteriaQuery msgQuery = cb.createQuery(String.class); + final Rootas = msgQuery.from(JpaActionStatus.class); + final ListJoin join = as.joinList("messages", JoinType.LEFT); + final CriteriaQuery selMsgQuery = msgQuery.select(join); + selMsgQuery.where(cb.equal(as.get(JpaActionStatus_.id), actionStatusId)); + + final List result = entityManager.createQuery(selMsgQuery).setFirstResult(pageable.getOffset()) + .setMaxResults(pageable.getPageSize()).getResultList().stream().collect(Collectors.toList()); + + return new PageImpl<>(result, pageable, totalCount); + } + @Override public Page findActionStatusAll(final Pageable pageable) { return convertAcSPage(actionStatusRepository.findAll(pageable), pageable); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java index db1301818..b99d67946 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java @@ -30,13 +30,12 @@ import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedExcepti import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; -import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.Status; -import org.eclipse.hawkbit.repository.model.ActionWithStatusCount; +import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetTag; @@ -53,6 +52,7 @@ import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort.Direction; import com.google.common.collect.Iterables; @@ -148,24 +148,65 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest { } @Test - @Description("Test verifies that the custom query to find all actions include the count of action status is working correctly") - public void findActionsWithStatusCountByTarget() { + @Description("Test verifies that actions of a target are found by using id-based search.") + public void findActionByTargetId() { final DistributionSet testDs = testdataFactory.createDistributionSet("TestDs", "1.0", new ArrayList()); final List testTarget = testdataFactory.createTargets(1); // one action with one action status is generated - final Action action = deploymentManagement - .findActionWithDetails(assignDistributionSet(testDs, testTarget).getActions().get(0)).get(); - // save 2 action status - actionStatusRepository.save(new JpaActionStatus(action, Status.RETRIEVED, System.currentTimeMillis())); - actionStatusRepository.save(new JpaActionStatus(action, Status.RUNNING, System.currentTimeMillis())); + final Long actionId = assignDistributionSet(testDs, testTarget).getActions().get(0); - final List findActionsWithStatusCountByTarget = deploymentManagement - .findActionsWithStatusCountByTargetOrderByIdDesc(testTarget.get(0).getControllerId()); + // act + final Slice actions = deploymentManagement.findActionsByTarget(testTarget.get(0).getControllerId(), + pageReq); + final Long count = deploymentManagement.countActionsByTarget(testTarget.get(0).getControllerId()); - assertThat(findActionsWithStatusCountByTarget).as("wrong action size").hasSize(1); - assertThat(findActionsWithStatusCountByTarget.get(0).getActionStatusCount()).as("wrong action status size") - .isEqualTo(3); + assertThat(count).as("One Action for target").isEqualTo(1L).isEqualTo(actions.getContent().size()); + assertThat(actions.getContent().get(0).getId()).as("Action of target").isEqualTo(actionId); + } + + @Test + @Description("Test verifies that action-states of an action are found by using id-based search.") + public void findActionStatusByActionId() { + final DistributionSet testDs = testdataFactory.createDistributionSet("TestDs", "1.0", + new ArrayList()); + final List testTarget = testdataFactory.createTargets(1); + // one action with one action status is generated + final Long actionId = assignDistributionSet(testDs, testTarget).getActions().get(0); + final Slice actions = deploymentManagement.findActionsByTarget(testTarget.get(0).getControllerId(), + pageReq); + final ActionStatus expectedActionStatus = actions.getContent().get(0).getActionStatus().get(0); + + // act + final Page actionStates = deploymentManagement.findActionStatusByAction(pageReq, actionId); + + assertThat(actionStates.getContent()).hasSize(1); + assertThat(actionStates.getContent().get(0)).as("Action-status of action").isEqualTo(expectedActionStatus); + } + + @Test + @Description("Test verifies that messages of an action-status are found by using id-based search.") + public void findMessagesByActionStatusId() { + final DistributionSet testDs = testdataFactory.createDistributionSet("TestDs", "1.0", + new ArrayList()); + final List testTarget = testdataFactory.createTargets(1); + // one action with one action status is generated + final Long actionId = assignDistributionSet(testDs, testTarget).getActions().get(0); + // create action-status entry with one message + controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId) + .status(Action.Status.FINISHED).messages(Lists.newArrayList("finished message"))); + final Page actionStates = deploymentManagement.findActionStatusByAction(pageReq, actionId); + // find newly created action-status entry with message + final ActionStatus actionStatusWithMessage = actionStates.getContent().stream() + .filter(entry -> entry.getMessages() != null && entry.getMessages().size() > 0).findFirst().get(); + final String expectedMsg = actionStatusWithMessage.getMessages().get(0); + + // act + final Page messages = deploymentManagement.findMessagesByActionStatusId(pageReq, + actionStatusWithMessage.getId()); + + assertThat(actionStates.getTotalElements()).as("Two action-states in total").isEqualTo(2L); + assertThat(messages.getContent().get(0)).as("Message of action-status").isEqualTo(expectedMsg); } @Test @@ -176,7 +217,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest { assignDS.add(testdataFactory.createDistributionSet("DS" + i, "1.0", Collections.emptyList()).getId()); } // not exists - assignDS.add(Long.valueOf(100)); + assignDS.add(100L); final DistributionSetTag tag = tagManagement .createDistributionSetTag(entityFactory.tag().create().name("Tag1")); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUI.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUI.java index 9575aa5b6..931873038 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUI.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUI.java @@ -8,8 +8,6 @@ */ package org.eclipse.hawkbit.ui; -import java.io.IOException; -import java.io.InputStream; import java.util.Locale; import java.util.Set; @@ -28,7 +26,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.core.io.Resource; import org.vaadin.spring.events.EventBus; import com.vaadin.annotations.Title; @@ -44,7 +41,6 @@ import com.vaadin.spring.navigator.SpringViewProvider; import com.vaadin.ui.Alignment; import com.vaadin.ui.Component; import com.vaadin.ui.CssLayout; -import com.vaadin.ui.CustomLayout; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Panel; @@ -84,6 +80,8 @@ public class HawkbitUI extends DefaultHawkbitUI implements DetachListener { @Autowired private NotificationUnreadButton notificationUnreadButton; + private Label viewTitle; + /** * Constructor taking the push strategy. * @@ -120,47 +118,22 @@ public class HawkbitUI extends DefaultHawkbitUI implements DetachListener { rootLayout.setSizeFull(); dashboardMenu.init(); - dashboardMenu.setResponsive(Boolean.TRUE); + dashboardMenu.setResponsive(true); final VerticalLayout contentVerticalLayout = new VerticalLayout(); - contentVerticalLayout.addComponent(buildHeader()); contentVerticalLayout.setSizeFull(); + contentVerticalLayout.setStyleName("main-content"); + contentVerticalLayout.addComponent(buildHeader()); + contentVerticalLayout.addComponent(buildViewTitle()); + + final Panel content = buildContent(); + contentVerticalLayout.addComponent(content); + contentVerticalLayout.setExpandRatio(content, 1); rootLayout.addComponent(dashboardMenu); rootLayout.addComponent(contentVerticalLayout); - - final HorizontalLayout viewHeadercontent = new HorizontalLayout(); - contentVerticalLayout.addComponent(viewHeadercontent); - viewHeadercontent.setWidth("100%"); - viewHeadercontent.setHeight("43px"); - viewHeadercontent.addStyleName("view-header-layout"); - - final Label viewHeader = new Label(); - viewHeader.setWidth("100%"); - viewHeader.setStyleName("header-content"); - viewHeadercontent.addComponent(viewHeader); - - viewHeadercontent.addComponent(notificationUnreadButton); - viewHeadercontent.setComponentAlignment(notificationUnreadButton, Alignment.MIDDLE_RIGHT); - - final Panel content = new Panel(); - content.setSizeFull(); - content.setStyleName("view-content"); - contentVerticalLayout.addComponent(content); - - rootLayout.setExpandRatio(contentVerticalLayout, 1.0F); - contentVerticalLayout.setStyleName("main-content"); - contentVerticalLayout.setExpandRatio(content, 1.0F); + rootLayout.setExpandRatio(contentVerticalLayout, 1); setContent(rootLayout); - final Resource resource = context - .getResource("classpath:/VAADIN/themes/" + UI.getCurrent().getTheme() + "/layouts/footer.html"); - try (InputStream resourceStream = resource.getInputStream()) { - final CustomLayout customLayout = new CustomLayout(resourceStream); - customLayout.setSizeUndefined(); - contentVerticalLayout.addComponent(customLayout); - } catch (final IOException ex) { - LOG.error("Footer file cannot be loaded", ex); - } final Navigator navigator = new Navigator(this, content); navigator.addViewChangeListener(new ViewChangeListener() { @@ -176,10 +149,10 @@ public class HawkbitUI extends DefaultHawkbitUI implements DetachListener { final DashboardMenuItem view = dashboardMenu.getByViewName(event.getViewName()); dashboardMenu.postViewChange(new PostViewChangeEvent(view)); if (view == null) { - viewHeader.setCaption(null); + viewTitle.setCaption(null); return; } - viewHeader.setCaption(view.getDashboardCaptionLong()); + viewTitle.setCaption(view.getDashboardCaptionLong()); notificationUnreadButton.setCurrentView(event.getNewView()); } }); @@ -200,6 +173,28 @@ public class HawkbitUI extends DefaultHawkbitUI implements DetachListener { LOG.info("Current locale of the application is : {}", HawkbitCommonUtil.getLocale()); } + private Panel buildContent() { + final Panel content = new Panel(); + content.setSizeFull(); + content.setStyleName("view-content"); + return content; + } + + private HorizontalLayout buildViewTitle() { + final HorizontalLayout viewHeadercontent = new HorizontalLayout(); + viewHeadercontent.setWidth("100%"); + viewHeadercontent.addStyleName("view-header-layout"); + + viewTitle = new Label(); + viewTitle.setWidth("100%"); + viewTitle.setStyleName("header-content"); + viewHeadercontent.addComponent(viewTitle); + + viewHeadercontent.addComponent(notificationUnreadButton); + viewHeadercontent.setComponentAlignment(notificationUnreadButton, Alignment.MIDDLE_RIGHT); + return viewHeadercontent; + } + private Component buildHeader() { final CssLayout cssLayout = new CssLayout(); cssLayout.setStyleName("view-header"); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java index 8a43b6388..b4cf015e7 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java @@ -467,12 +467,12 @@ public class ArtifactDetailsLayout extends VerticalLayout { } private void showMinIcon() { - maxMinButton.togleIcon(FontAwesome.COMPRESS); + maxMinButton.toggleIcon(FontAwesome.COMPRESS); maxMinButton.setData(Boolean.TRUE); } private void showMaxIcon() { - maxMinButton.togleIcon(FontAwesome.EXPAND); + maxMinButton.toggleIcon(FontAwesome.EXPAND); maxMinButton.setData(Boolean.FALSE); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGrid.java index 180f9130d..b5775008c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGrid.java @@ -8,40 +8,70 @@ */ package org.eclipse.hawkbit.ui.common.grid; +import java.util.Locale; + +import org.apache.commons.lang3.ArrayUtils; import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.components.RefreshableContainer; -import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; import org.vaadin.spring.events.EventBus; import org.vaadin.spring.events.EventBus.UIEventBus; -import com.vaadin.data.Container; import com.vaadin.data.Container.Indexed; +import com.vaadin.data.util.GeneratedPropertyContainer; +import com.vaadin.data.util.converter.Converter; import com.vaadin.ui.Grid; /** - * Abstract table class. + * Abstract grid that offers various capabilities (aka support) to offer + * convenient enhancements to the vaadin standard grid. * + * @param + * The container-type used by the grid */ -public abstract class AbstractGrid extends Grid implements RefreshableContainer { - - private static final long serialVersionUID = 4856562746502217630L; +public abstract class AbstractGrid extends Grid implements RefreshableContainer { + private static final long serialVersionUID = 1L; protected final VaadinMessageSource i18n; - protected final transient EventBus.UIEventBus eventBus; - protected final SpPermissionChecker permissionChecker; - protected AbstractGrid(final VaadinMessageSource i18n, final UIEventBus eventBus, final SpPermissionChecker permissionChecker) { + private transient AbstractMaximizeSupport maximizeSupport; + private transient AbstractGeneratedPropertySupport generatedPropertySupport; + private transient SingleSelectionSupport singleSelectionSupport; + private transient DetailsSupport detailsSupport; + + /** + * Constructor. + * + * @param i18n + * @param eventBus + * @param permissionChecker + */ + protected AbstractGrid(final VaadinMessageSource i18n, final UIEventBus eventBus, + final SpPermissionChecker permissionChecker) { this.i18n = i18n; this.eventBus = eventBus; this.permissionChecker = permissionChecker; + } + + /** + * Initializes the grid. + *

+ * + * NOTE: Sub-classes should configure the grid before calling this + * method (this means: set all support-classes needed, and then call init). + */ + protected void init() { setSizeFull(); setImmediate(true); setId(getGridId()); - setSelectionMode(SelectionMode.NONE); + if (!hasSingleSelectionSupport()) { + setSelectionMode(SelectionMode.NONE); + } setColumnReorderingAllowed(true); addNewContainerDS(); eventBus.subscribe(this); @@ -52,56 +82,593 @@ public abstract class AbstractGrid extends Grid implements RefreshableContainer */ @Override public void refreshContainer() { - final Container container = getContainerDataSource(); - if (!(container instanceof LazyQueryContainer)) { + final Indexed container = getContainerDataSource(); + if (hasGeneratedPropertySupport() + && getGeneratedPropertySupport().getRawContainer() instanceof LazyQueryContainer) { + ((LazyQueryContainer) getGeneratedPropertySupport().getRawContainer()).refresh(); return; } - ((LazyQueryContainer) container).refresh(); + + if (container instanceof LazyQueryContainer) { + ((LazyQueryContainer) container).refresh(); + } } - private void addNewContainerDS() { - final Container container = createContainer(); - setContainerDataSource((Indexed) container); + /** + * Creates a new container instance by calling the required + * template-methods. + *

+ * A new container is created on initialization as well as when container + * content fundamentally changes (e.g. if container content depends on a + * selection as common in master-details relations) + */ + protected void addNewContainerDS() { + final T container = createContainer(); + Indexed indexedContainer = container; + if (hasGeneratedPropertySupport()) { + indexedContainer = getGeneratedPropertySupport().decorate(container); + setContainerDataSource(indexedContainer); + getGeneratedPropertySupport().addGeneratedContainerProperties(); + } else { + setContainerDataSource(indexedContainer); + } addContainerProperties(); - setColumnExpandRatio(); + setColumnProperties(); setColumnHeaderNames(); + setColumnsHidable(); addColumnRenderes(); + setColumnExpandRatio(); + + setHiddenColumns(); final CellDescriptionGenerator cellDescriptionGenerator = getDescriptionGenerator(); if (getDescriptionGenerator() != null) { setCellDescriptionGenerator(cellDescriptionGenerator); } - // Allow column hiding - for (final Column c : getColumns()) { - c.setHidable(true); - } - setHiddenColumns(); - int size = 0; - if (container != null) { - size = container.size(); - } - if (size == 0) { + if (indexedContainer != null && indexedContainer.size() == 0) { setData(SPUIDefinitions.NO_DATA); } } - protected abstract Container createContainer(); + /** + * Sets the standard behavior of columns to be hidable. If implementors + * needs other behavior they have to concern about it. + */ + protected void setColumnsHidable() { + // Allow column hiding + for (final Column c : getColumns()) { + c.setHidable(true); + } + } + /** + * Enables maximize-support for the grid by setting a MaximizeSupport + * implementation. + * + * @param maximizeSupport + * encapsulates behavior for minimize and maximize. + */ + protected void setMaximizeSupport(final AbstractMaximizeSupport maximizeSupport) { + this.maximizeSupport = maximizeSupport; + } + + /** + * Gets the MaximizeSupport implementation describing behavior for minimize + * and maximize. + * + * @return maximizeSupport that encapsulates behavior for minimize and + * maximize. + */ + protected AbstractMaximizeSupport getMaximizeSupport() { + return maximizeSupport; + } + + /** + * Checks whether maximize-support is enabled. + * + * @return true if maximize-support is enabled, otherwise + * false + */ + protected boolean hasMaximizeSupport() { + return maximizeSupport != null; + } + + /** + * Enables support for generated properties. This implies that the + * standard-container has to be decorated and the generators have to be + * registered for the generated (aka virtual) properties. + * + * @param generatedPropertySupport + * that encapsulates behavior for generated properties + */ + protected void setGeneratedPropertySupport(final AbstractGeneratedPropertySupport generatedPropertySupport) { + this.generatedPropertySupport = generatedPropertySupport; + } + + /** + * Gets the GeneratedPropertySupport implementation describing generated + * properties by registering their generators and attaching them to a + * wrapper-container. + * + * @return generatedPropertySupport that encapsulates registration of + * generated properties. + */ + protected AbstractGeneratedPropertySupport getGeneratedPropertySupport() { + return generatedPropertySupport; + } + + /** + * Checks whether support for generated properties is enabled. + * + * @return true if support for generated properties is enabled, + * otherwise false + */ + protected boolean hasGeneratedPropertySupport() { + return generatedPropertySupport != null; + } + + /** + * Enables single-selection-support for the grid by setting + * SingleSelectionSupport configuration. + * + * @param singleSelectionSupport + * encapsulates behavior for single-selection and offers some + * convenient functionality. + */ + protected void setSingleSelectionSupport(final SingleSelectionSupport singleSelectionSupport) { + this.singleSelectionSupport = singleSelectionSupport; + } + + /** + * Gets the SingleSelectionSupport implementation configuring + * single-selection. + * + * @return singleSelectionSupport that configures single-selection. + */ + protected SingleSelectionSupport getSingleSelectionSupport() { + return singleSelectionSupport; + } + + /** + * Checks whether single-selection-support is enabled. + * + * @return true if single-selection-support is enabled, + * otherwise false + */ + protected boolean hasSingleSelectionSupport() { + return singleSelectionSupport != null; + } + + /** + * Enables details-support for the grid by setting DetailsSupport + * configuration. If details-support is enabled, the grid handles + * details-data that depends on a master-selection. + * + * @param detailsSupport + * encapsulates behavior for changes of master-selection. + */ + protected void setDetailsSupport(final DetailsSupport detailsSupport) { + this.detailsSupport = detailsSupport; + } + + /** + * Gets the DetailsSupport implementation configuring master-details + * relation. + * + * @return detailsSupport that configures master-details relation. + */ + public DetailsSupport getDetailsSupport() { + return detailsSupport; + } + + /** + * Checks whether details-support is enabled. + * + * @return true if details-support is enabled, otherwise + * false + */ + public boolean hasDetailsSupport() { + return detailsSupport != null; + } + + /** + * Template method invoked by {@link this#addNewContainerDS()} for creating + * a container instance. + * + * @return new container instance used by the grid. + */ + protected abstract T createContainer(); + + /** + * Template method invoked by {@link this#addNewContainerDS()} for adding + * properties to the container (usually by invoing + * {@link Container#addContainerProperty(Object, Class, Object))}) + */ protected abstract void addContainerProperties(); + /** + * Template method invoked by {@link this#addNewContainerDS()} for setting + * the expand ratio of the columns. + */ protected abstract void setColumnExpandRatio(); + /** + * Template method invoked by {@link this#addNewContainerDS()} for setting + * the column names. + */ protected abstract void setColumnHeaderNames(); - protected abstract String getGridId(); - + /** + * Template method invoked by {@link this#addNewContainerDS()} for setting + * the column properties to the grid. + */ protected abstract void setColumnProperties(); + /** + * Template method invoked by {@link this#addNewContainerDS()} for adding + * special column renderers if needed. + */ protected abstract void addColumnRenderes(); + /** + * Template method invoked by {@link this#addNewContainerDS()} that hides + * columns. If a column is hidable and hidden, it can be made visible via + * grid column menu. + */ protected abstract void setHiddenColumns(); + /** + * Template method invoked by {@link this#addNewContainerDS()} for adding a + * CellDescriptionGenerator to the grid. + */ protected abstract CellDescriptionGenerator getDescriptionGenerator(); + + /** + * Gets id of the grid. + * + * @return id of the grid + */ + protected abstract String getGridId(); + + /** + * Resets the default row of the header. This means the current default row + * is removed and replaced with a newly created one. + * + * @return the new and clean header row. + */ + protected HeaderRow resetHeaderDefaultRow() { + getHeader().removeRow(getHeader().getDefaultRow()); + final HeaderRow newHeaderRow = getHeader().appendRow(); + getHeader().setDefaultRow(newHeaderRow); + return newHeaderRow; + } + + /** + * Support for master-details relation for grid. This means that grid + * content (=details) is updated as soon as master-data changes. + */ + public class DetailsSupport { + + private Long master; + + /** + * Set selected master-data as member of this grid-support (as all + * presented grid-data is related to this master-data) and re-calculate + * grid-container-content. + * + * @param master + * id of selected action + */ + public void populateMasterDataAndRecalculateContainer(final Long master) { + this.master = master; + recalculateContainer(); + populateSelection(); + } + + /** + * Set selected master-data as member of this grid-support (as all + * presented grid-data is related to this master-data) and re-create + * grid-container. + * + * @param master + * id of selected action + */ + public void populateMasterDataAndRecreateContainer(final Long master) { + this.master = master; + recreateContainer(); + populateSelection(); + } + + /** + * Propagates the selection if needed. + * + */ + public void populateSelection() { + if (!hasSingleSelectionSupport()) { + return; + } + + if (master == null) { + getSingleSelectionSupport().clearSelection(); + return; + } + getSingleSelectionSupport().selectFirstRow(); + } + + /** + * Gets the master-data id. + * + * @return master-data id + */ + public Long getMasterDataId() { + return master; + } + + /** + * Invalidates container-data (but reused container) and refreshes it + * with new details-data for the new selected master-data. + */ + private void recalculateContainer() { + clearSortOrder(); + refreshContainer(); + } + + /** + * Invalidates container and replace it with a fresh instance for the + * new selected master-data. + */ + private void recreateContainer() { + removeAllColumns(); + clearSortOrder(); + addNewContainerDS(); + } + } + + /** + * Via implementations of this support capability an expand-mode is provided + * that maximizes the grid size. + */ + protected abstract class AbstractMaximizeSupport { + + /** + * Renews the content for maximized layout. + */ + public void createMaximizedContent() { + setMaximizedColumnProperties(); + setMaximizedHiddenColumns(); + setMaximizedHeaders(); + setMaximizedColumnExpandRatio(); + } + + /** + * Renews the content for minimized layout. + */ + public void createMinimizedContent() { + setColumnProperties(); + setHiddenColumns(); + setColumnExpandRatio(); + } + + /** + * Sets the column properties for maximized-state. + */ + protected abstract void setMaximizedColumnProperties(); + + /** + * Sets the hidden columns for maximized-state. + */ + protected abstract void setMaximizedHiddenColumns(); + + /** + * Sets additional headers for maximized-state. + */ + protected abstract void setMaximizedHeaders(); + + /** + * Sets column expand ratio for maximized-state. + */ + protected abstract void setMaximizedColumnExpandRatio(); + } + + /** + * Grids that are used in conjunction with + * {@link GeneratedPropertyContainer}, might use + * {@link AbstractGeneratedPropertySupport} to get type-save access to the + * raw container as well as the decorated container. + */ + protected abstract class AbstractGeneratedPropertySupport { + + /** + * Gives type-save access to the wrapper container the grid works on. + * This wrapper container attaches generated properties that are not + * part of the raw container that encapsulates the database access. + * + * @return decorated container that includes the generated properties as + * well as the native properties. + */ + public abstract GeneratedPropertyContainer getDecoratedContainer(); + + /** + * Gives type-save access to the wrapped container that binds to the + * database. The grid does not work directly with this container but + * with a container that wraps and decorates it with generated + * properties. + * + * @return raw container that gives access to the native properties. + */ + public abstract T getRawContainer(); + + /** + * Adds the generated properties to the decorated container. Each + * generated property has to be associated with a property generator + * that is capable to calculate the value of the generated (aka virtual) + * property. + * + * @return decorated container that includes the generated properties + */ + protected abstract GeneratedPropertyContainer addGeneratedContainerProperties(); + + /** + * Decorates the raw-container by wrapping it. + * + * @param container + * raw-container to be wrapped. + * @return decorated container. + */ + protected GeneratedPropertyContainer decorate(final T container) { + return new GeneratedPropertyContainer(container); + } + } + + /** + * Support for single selection on the grid. + */ + protected class SingleSelectionSupport { + + public SingleSelectionSupport() { + enable(); + } + + public final void enable() { + setSelectionMode(SelectionMode.SINGLE); + } + + public final void disable() { + setSelectionMode(SelectionMode.NONE); + } + + /** + * Selects the first row if available and enabled. + */ + public void selectFirstRow() { + if (!isSingleSelectionModel()) { + return; + } + + final Indexed container = getContainerDataSource(); + final int size = container.size(); + if (size > 0) { + refreshRows(getContainerDataSource().firstItemId()); + getSingleSelectionModel().select(getContainerDataSource().firstItemId()); + } else { + getSingleSelectionModel().select(null); + } + } + + private boolean isSingleSelectionModel() { + return getSelectionModel() instanceof SelectionModel.Single; + } + + /** + * Clears the selection. + */ + public void clearSelection() { + if (!isSingleSelectionModel()) { + return; + } + getSingleSelectionModel().select(null); + } + + private SelectionModel.Single getSingleSelectionModel() { + return (SelectionModel.Single) getSelectionModel(); + } + } + + /** + * CellStyleGenerator that concerns about alignment in the grid cells. + */ + protected static class AlignCellStyleGenerator implements CellStyleGenerator { + private static final long serialVersionUID = 5573570647129792429L; + + private final String[] left; + private final String[] center; + private final String[] right; + + /** + * Constructor. + * + * @param left + * list of propertyIds that should be left-aligned + * @param center + * list of propertyIds that should be center-aligned + * @param right + * list of propertyIds that should be right-aligned + */ + public AlignCellStyleGenerator(final String[] left, final String[] center, final String[] right) { + this.left = left; + this.center = center; + this.right = right; + } + + @Override + public String getStyle(final CellReference cellReference) { + if (ArrayUtils.contains(center, cellReference.getPropertyId())) { + return "centeralign"; + } else if (ArrayUtils.contains(right, cellReference.getPropertyId())) { + return "rightalign"; + } else if (ArrayUtils.contains(left, cellReference.getPropertyId())) { + return "leftalign"; + } + return null; + } + } + + /** + * Adds a tooltip to the 'Date and time' column in detailed format. + */ + public static class ModifiedTimeTooltipGenerator implements CellDescriptionGenerator { + private static final long serialVersionUID = -6617911967167729195L; + + private final String datePropertyId; + + /** + * Constructor. + * + * @param datePropertyId + */ + public ModifiedTimeTooltipGenerator(final String datePropertyId) { + this.datePropertyId = datePropertyId; + } + + @Override + public String getDescription(final CellReference cell) { + if (!datePropertyId.equals(cell.getPropertyId())) { + return null; + } + final Long timestamp = (Long) cell.getItem().getItemProperty(datePropertyId).getValue(); + return SPDateTimeUtil.getFormattedDate(timestamp); + } + } + + /** + * Converter that gets time-data as input of type Long and + * converts to a formatted date string. + */ + public class LongToFormattedDateStringConverter implements Converter { + private static final long serialVersionUID = 1247513913478717845L; + + @Override + public Long convertToModel(final String value, final Class targetType, final Locale locale) { + // not needed + return null; + } + + @Override + public String convertToPresentation(final Long value, final Class targetType, + final Locale locale) { + return SPDateTimeUtil.getFormattedDate(value, SPUIDefinitions.LAST_QUERY_DATE_FORMAT_SHORT); + } + + @Override + public Class getModelType() { + return Long.class; + } + + @Override + public Class getPresentationType() { + return String.class; + } + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridComponentLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridComponentLayout.java new file mode 100644 index 000000000..2e97293b4 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridComponentLayout.java @@ -0,0 +1,201 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.common.grid; + +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventBus.UIEventBus; + +import com.vaadin.ui.AbstractOrderedLayout; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Grid; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.Layout; +import com.vaadin.ui.VerticalLayout; + +/** + * Abstract grid layout class which builds layout with grid {@link AbstractGrid} + * and grid header {@link DefaultGridHeader}. + */ +public abstract class AbstractGridComponentLayout extends VerticalLayout { + private static final long serialVersionUID = -3766179797384539821L; + + protected final transient EventBus.UIEventBus eventBus; + protected final VaadinMessageSource i18n; + + private AbstractOrderedLayout gridHeader; + private Grid grid; + + private transient AbstractFooterSupport footerSupport; + + /** + * Constructor. + * + * @param i18n + * @param deploymentManagement + * @param eventBus + * @param notification + * @param managementUIState + */ + public AbstractGridComponentLayout(final VaadinMessageSource i18n, final UIEventBus eventBus) { + super(); + this.i18n = i18n; + this.eventBus = eventBus; + } + + /** + * Initializes this layout that presents a header and a grid. + */ + protected void init() { + this.gridHeader = createGridHeader(); + this.grid = createGrid(); + buildLayout(); + setSizeFull(); + setImmediate(true); + eventBus.subscribe(this); + } + + /** + * Layouts header, grid and optional footer. + */ + protected void buildLayout() { + setSizeFull(); + setSpacing(true); + setMargin(false); + setStyleName("group"); + final VerticalLayout gridHeaderLayout = new VerticalLayout(); + gridHeaderLayout.setSizeFull(); + gridHeaderLayout.setSpacing(false); + gridHeaderLayout.setMargin(false); + + gridHeaderLayout.setStyleName("table-layout"); + gridHeaderLayout.addComponent(gridHeader); + + gridHeaderLayout.setComponentAlignment(gridHeader, Alignment.TOP_CENTER); + gridHeaderLayout.addComponent(grid); + gridHeaderLayout.setComponentAlignment(grid, Alignment.TOP_CENTER); + gridHeaderLayout.setExpandRatio(grid, 1.0F); + + addComponent(gridHeaderLayout); + setComponentAlignment(gridHeaderLayout, Alignment.TOP_CENTER); + setExpandRatio(gridHeaderLayout, 1.0F); + if (hasFooterSupport()) { + final Layout footerLayout = getFooterSupport().createFooterMessageComponent(); + addComponent(footerLayout); + setComponentAlignment(footerLayout, Alignment.BOTTOM_CENTER); + } + + } + + /** + * Registers the selection of this grid as master for another grid that + * displays the details. + * + * @param details + * the details of another grid the selection of this grid should + * be registered for as master. + */ + public void registerDetails(final AbstractGrid.DetailsSupport details) { + grid.addSelectionListener(event -> { + final Long masterId = (Long) event.getSelected().stream().findFirst().orElse(null); + details.populateMasterDataAndRecalculateContainer(masterId); + }); + } + + /** + * Gets the grid instance displayed and owned by the layout. + * + * @return grid instance displayed and owned by the layout. + */ + public Grid getGrid() { + return grid; + } + + /** + * Gets the grid-header instance displayed and owned by the layout. + * + * @return grid-header instance displayed and owned by the layout. + */ + public AbstractOrderedLayout getHeader() { + return gridHeader; + } + + /** + * Creates the grid-header instance the layout is responsible for. + * + * @return newly created grid-header instance displayed and owned by the + * layout. + */ + public abstract AbstractOrderedLayout createGridHeader(); + + /** + * Creates the grid instance the layout is responsible for. + * + * @return newly created grid instance displayed and owned by the layout. + */ + public abstract Grid createGrid(); + + /** + * Enables footer-support for the grid by setting a FooterSupport + * implementation. + * + * @param footerSupport + * encapsulates footer layout. + */ + public void setFooterSupport(final AbstractFooterSupport footerSupport) { + this.footerSupport = footerSupport; + } + + /** + * Gets the FooterSupport implementation describing footer layout. + * + * @return footerSupport that encapsulates footer layout. + */ + public AbstractFooterSupport getFooterSupport() { + return footerSupport; + } + + /** + * Checks whether footer-support is enabled. + * + * @return true if footer-support is enabled, otherwise + * false + */ + public boolean hasFooterSupport() { + return footerSupport != null; + } + + /** + * If footer support is enabled, the footer is placed below the component + */ + public abstract class AbstractFooterSupport { + + /** + * Creates a sub-layout for the footer. + * + * @return the footer sub-layout. + */ + private Layout createFooterMessageComponent() { + final HorizontalLayout footerLayout = new HorizontalLayout(); + footerLayout.addComponent(getFooterMessageLabel()); + footerLayout.setStyleName(SPUIStyleDefinitions.FOOTER_LAYOUT); + footerLayout.setWidth(100, Unit.PERCENTAGE); + return footerLayout; + } + + /** + * Get the count message label. + * + * @return count message + */ + protected abstract Label getFooterMessageLabel(); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridHeader.java index 28aa1ada9..47aba63f5 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridHeader.java @@ -152,7 +152,7 @@ public abstract class AbstractGridHeader extends VerticalLayout { private void openSearchTextField() { searchResetIcon.addStyleName(SPUIDefinitions.FILTER_RESET_ICON); - searchResetIcon.togleIcon(FontAwesome.TIMES); + searchResetIcon.toggleIcon(FontAwesome.TIMES); searchResetIcon.setData(Boolean.TRUE); searchField.removeStyleName(SPUIDefinitions.FILTER_BOX_HIDE); searchField.setVisible(true); @@ -164,7 +164,7 @@ public abstract class AbstractGridHeader extends VerticalLayout { searchField.addStyleName(SPUIDefinitions.FILTER_BOX_HIDE); searchField.setVisible(false); searchResetIcon.removeStyleName(SPUIDefinitions.FILTER_RESET_ICON); - searchResetIcon.togleIcon(FontAwesome.SEARCH); + searchResetIcon.toggleIcon(FontAwesome.SEARCH); searchResetIcon.setData(Boolean.FALSE); resetSearchText(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridLayout.java deleted file mode 100644 index 711f9712c..000000000 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridLayout.java +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations 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.common.grid; - -import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; -import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; - -import com.vaadin.ui.Alignment; -import com.vaadin.ui.HorizontalLayout; -import com.vaadin.ui.Label; -import com.vaadin.ui.VerticalLayout; - -/** - * - * Abstract grid layout class which builds layout with grid {@link AbstractGrid} - * and table header {@link AbstractGridHeader}. - * - */ -public abstract class AbstractGridLayout extends VerticalLayout { - - private static final long serialVersionUID = 8611248179949245460L; - - private final AbstractGridHeader tableHeader; - - protected final AbstractGrid grid; - - protected AbstractGridLayout(final AbstractGridHeader tableHeader, final AbstractGrid grid) { - this.tableHeader = tableHeader; - this.grid = grid; - } - - protected void buildLayout() { - setSizeFull(); - setSpacing(true); - setMargin(false); - setStyleName("group"); - final VerticalLayout tableHeaderLayout = new VerticalLayout(); - tableHeaderLayout.setSizeFull(); - tableHeaderLayout.setSpacing(false); - tableHeaderLayout.setMargin(false); - - tableHeaderLayout.setStyleName("table-layout"); - tableHeaderLayout.addComponent(tableHeader); - - tableHeaderLayout.setComponentAlignment(tableHeader, Alignment.TOP_CENTER); - tableHeaderLayout.addComponent(grid); - tableHeaderLayout.setComponentAlignment(grid, Alignment.TOP_CENTER); - tableHeaderLayout.setExpandRatio(grid, 1.0F); - - addComponent(tableHeaderLayout); - setComponentAlignment(tableHeaderLayout, Alignment.TOP_CENTER); - setExpandRatio(tableHeaderLayout, 1.0F); - if (hasCountMessage()) { - final HorizontalLayout rolloutGroupTargetsCountLayout = createCountMessageComponent(); - addComponent(rolloutGroupTargetsCountLayout); - setComponentAlignment(rolloutGroupTargetsCountLayout, Alignment.BOTTOM_CENTER); - } - - } - - private HorizontalLayout createCountMessageComponent() { - final HorizontalLayout rolloutGroupTargetsCountLayout = new HorizontalLayout(); - final Label countMessageLabel = getCountMessageLabel(); - countMessageLabel.setId(UIComponentIdProvider.ROLLOUT_GROUP_TARGET_LABEL); - rolloutGroupTargetsCountLayout.addComponent(getCountMessageLabel()); - rolloutGroupTargetsCountLayout.setStyleName(SPUIStyleDefinitions.FOOTER_LAYOUT); - rolloutGroupTargetsCountLayout.setWidth("100%"); - return rolloutGroupTargetsCountLayout; - - } - - /** - * Only in rollout group targets view count message is displayed. - * - * @return true if count message has to be displayed - */ - protected abstract boolean hasCountMessage(); - - /** - * Get the count message label. - * - * @return count message - */ - protected abstract Label getCountMessageLabel(); - - public AbstractGrid getGrid() { - return grid; - } - -} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/DefaultGridHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/DefaultGridHeader.java new file mode 100644 index 000000000..5f9aec220 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/DefaultGridHeader.java @@ -0,0 +1,243 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.common.grid; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.hawkbit.ui.common.builder.LabelBuilder; +import org.eclipse.hawkbit.ui.components.SPUIButton; +import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; +import org.eclipse.hawkbit.ui.management.state.ManagementUIState; +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; + +import com.vaadin.server.FontAwesome; +import com.vaadin.shared.ui.label.ContentMode; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.VerticalLayout; + +/** + * Abstract grid header placed on top of a grid. + */ +public class DefaultGridHeader extends VerticalLayout { + private static final long serialVersionUID = 1921798400953670917L; + + private final ManagementUIState managementUIState; + + private final String titleText; + private Label title; + private HorizontalLayout titleLayout; + private transient AbstractHeaderMaximizeSupport maximizeSupport; + + /** + * Constructor. + * + * @param managementUIState + */ + public DefaultGridHeader(final ManagementUIState managementUIState) { + this(managementUIState, StringUtils.EMPTY); + } + + /** + * Constructor. + * + * @param managementUIState + * @param titleText + */ + public DefaultGridHeader(final ManagementUIState managementUIState, final String titleText) { + this.managementUIState = managementUIState; + this.titleText = titleText; + } + + /** + * Initializes the header. + * + * @return this DefaultGridHeader in order to allow method chaining + */ + public DefaultGridHeader init() { + buildTitleLabel(); + buildTitleLayout(); + buildComponent(); + return this; + } + + /** + * Builds the title label. + * + * @return title-label + */ + protected Label buildTitleLabel() { + // create default title - even shown when no data is available + title = new LabelBuilder().name(titleText).buildCaptionLabel(); + title.setImmediate(true); + title.setContentMode(ContentMode.HTML); + + return title; + } + + /** + * Builds the title layout. + * + * @return title-layout + */ + protected HorizontalLayout buildTitleLayout() { + titleLayout = new HorizontalLayout(); + titleLayout.addStyleName(SPUIStyleDefinitions.WIDGET_TITLE); + titleLayout.setSpacing(false); + titleLayout.setMargin(false); + titleLayout.setSizeFull(); + titleLayout.addComponent(title); + titleLayout.setComponentAlignment(title, Alignment.TOP_LEFT); + titleLayout.setExpandRatio(title, 0.8F); + + if (hasHeaderMaximizeSupport()) { + titleLayout.addComponents(getHeaderMaximizeSupport().maxMinButton); + titleLayout.setComponentAlignment(getHeaderMaximizeSupport().maxMinButton, Alignment.TOP_RIGHT); + titleLayout.setExpandRatio(getHeaderMaximizeSupport().maxMinButton, 0.2F); + } + + return titleLayout; + } + + /** + * Builds the layout for the header component. + */ + protected void buildComponent() { + addComponent(titleLayout); + setComponentAlignment(titleLayout, Alignment.TOP_LEFT); + setWidth(100, Unit.PERCENTAGE); + setImmediate(true); + addStyleName("action-history-header"); + addStyleName("bordered-layout"); + addStyleName("no-border-bottom"); + } + + /** + * Enables maximize-support for the header by setting a + * HeaderMaximizeSupport implementation. + * + * @param maximizeSupport + * encapsulates layout of min-max-button and behavior for + * minimize and maximize. + */ + public void setHeaderMaximizeSupport(final AbstractHeaderMaximizeSupport maximizeSupport) { + this.maximizeSupport = maximizeSupport; + } + + /** + * Gets the HeaderMaximizeSupport implementation describing behavior for + * minimize and maximize. + * + * @return maximizeSupport that encapsulates behavior for minimize and + * maximize. + */ + public AbstractHeaderMaximizeSupport getHeaderMaximizeSupport() { + return maximizeSupport; + } + + /** + * Checks whether maximize-support is enabled. + * + * @return true if maximize-support is enabled, otherwise + * false + */ + public boolean hasHeaderMaximizeSupport() { + return maximizeSupport != null; + } + + /** + * Updates the title of the header. + * + * @param newTitle + */ + public void updateTitle(final String newTitle) { + title.setValue(newTitle); + } + + /** + * The Implemented capability offers a button that triggers minimization and + * maximization. + */ + public abstract class AbstractHeaderMaximizeSupport { + + private final SPUIButton maxMinButton; + + /** + * Constructor. + * + * @param maximizeButtonId + */ + protected AbstractHeaderMaximizeSupport(final String maximizeButtonId) { + maxMinButton = createMinMaxButton(maximizeButtonId); + // listener for maximizing action history + maxMinButton.addClickListener(event -> maxMinButtonClicked()); + } + + /** + * Invoked when min-max-button is pressed. + */ + private void maxMinButtonClicked() { + final Boolean flag = (Boolean) maxMinButton.getData(); + if (flag == null || Boolean.FALSE.equals(flag)) { + // Clicked on max Icon + showMinIcon(); + maximize(); + managementUIState.setActionHistoryMaximized(true); + } else { + // Clicked on min icon + showMaxIcon(); + minimize(); + managementUIState.setActionHistoryMaximized(false); + } + } + + /** + * Additional actions for maximize operation might be performed by this + * method. + */ + protected abstract void maximize(); + + /** + * Additional actions for minimize operation might be performed by this + * method. + */ + protected abstract void minimize(); + + /** + * Creates a min-max-button instance. + * + * @param buttonId + * the button id for the min-max-button + * @return newly cretaed min-max-button + */ + protected SPUIButton createMinMaxButton(final String buttonId) { + return (SPUIButton) SPUIComponentProvider.getButton(buttonId, "", "Maximize", null, true, + FontAwesome.EXPAND, SPUIButtonStyleSmallNoBorder.class); + } + + /** + * Styles min-max-button icon with minimize decoration + */ + public void showMinIcon() { + maxMinButton.toggleIcon(FontAwesome.COMPRESS); + maxMinButton.setDescription("Minimize"); + maxMinButton.setData(Boolean.TRUE); + } + + /** + * Styles min-max-button icon with maximize decoration + */ + public void showMaxIcon() { + maxMinButton.toggleIcon(FontAwesome.EXPAND); + maxMinButton.setDescription("Maximize"); + maxMinButton.setData(Boolean.FALSE); + } + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java index 03bf215a1..5751cfb32 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/AbstractTableHeader.java @@ -258,7 +258,7 @@ public abstract class AbstractTableHeader extends VerticalLayout { private void openSearchTextField() { searchResetIcon.addStyleName(SPUIDefinitions.FILTER_RESET_ICON); - searchResetIcon.togleIcon(FontAwesome.TIMES); + searchResetIcon.toggleIcon(FontAwesome.TIMES); searchResetIcon.setData(Boolean.TRUE); searchField.removeStyleName(SPUIDefinitions.FILTER_BOX_HIDE); searchField.focus(); @@ -268,7 +268,7 @@ public abstract class AbstractTableHeader extends VerticalLayout { searchField.setValue(""); searchField.addStyleName(SPUIDefinitions.FILTER_BOX_HIDE); searchResetIcon.removeStyleName(SPUIDefinitions.FILTER_RESET_ICON); - searchResetIcon.togleIcon(FontAwesome.SEARCH); + searchResetIcon.toggleIcon(FontAwesome.SEARCH); searchResetIcon.setData(Boolean.FALSE); resetSearchText(); } @@ -297,12 +297,12 @@ public abstract class AbstractTableHeader extends VerticalLayout { } private void showMinIcon() { - maxMinIcon.togleIcon(FontAwesome.COMPRESS); + maxMinIcon.toggleIcon(FontAwesome.COMPRESS); maxMinIcon.setData(Boolean.TRUE); } private void showMaxIcon() { - maxMinIcon.togleIcon(FontAwesome.EXPAND); + maxMinIcon.toggleIcon(FontAwesome.EXPAND); maxMinIcon.setData(Boolean.FALSE); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/BaseUIEntityEvent.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/BaseUIEntityEvent.java index b16f891cc..7e4853677 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/BaseUIEntityEvent.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/table/BaseUIEntityEvent.java @@ -37,7 +37,7 @@ public class BaseUIEntityEvent { /** * Base entity event - * + * * @param eventType * the event type * @param entity @@ -55,7 +55,7 @@ public class BaseUIEntityEvent { /** * Base entity event - * + * * @param eventType * the event type * @param entityIds @@ -74,6 +74,10 @@ public class BaseUIEntityEvent { return entity; } + public Collection getEntityIds() { + return entityIds; + } + public BaseEntityEventType getEventType() { return eventType; } @@ -81,7 +85,7 @@ public class BaseUIEntityEvent { /** * Checks if the remote event is the same as this UI event. Then maybe you * can skip the remote event because it is already executed. - * + * * @param tenantAwareEvent * the remote event * @return {@code true} match ; {@code false} not match diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/NotificationUnreadButton.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/NotificationUnreadButton.java index 4b386ff93..5c24fa697 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/NotificationUnreadButton.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/NotificationUnreadButton.java @@ -93,7 +93,7 @@ public class NotificationUnreadButton extends Button { notificationsWindow.setClosable(true); notificationsWindow.setResizable(false); notificationsWindow.setDraggable(false); - notificationsWindow.setId(UIComponentIdProvider.NOTIFICATION_UNREAD_POPUP_id); + notificationsWindow.setId(UIComponentIdProvider.NOTIFICATION_UNREAD_POPUP_ID); notificationsWindow.addCloseListener(event -> refreshCaption()); notificationsWindow.addBlurListener(this::closeWindow); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/SPUIButton.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/SPUIButton.java index 7f19bb5f2..3b5be7110 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/SPUIButton.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/SPUIButton.java @@ -27,12 +27,12 @@ public class SPUIButton extends Button { } /** - * Toogle Icon on action. - * + * Toggle Icon on action. + * * @param icon * as Resource */ - public void togleIcon(final Resource icon) { + public void toggleIcon(final Resource icon) { setIcon(icon); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/GridButtonRendererConnector.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/GridButtonRendererConnector.java new file mode 100644 index 000000000..d8ee9d6ac --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/GridButtonRendererConnector.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.customrenderers.client; + +import org.eclipse.hawkbit.ui.customrenderers.client.renderers.FontIconData; + +import com.google.web.bindery.event.shared.HandlerRegistration; +import com.vaadin.client.connectors.ClickableRendererConnector; +import com.vaadin.client.renderers.ClickableRenderer.RendererClickHandler; +import com.vaadin.shared.ui.Connect; + +import elemental.json.JsonObject; + +/** + * A connector for {@link GridButtonRenderer }. + * + */ +@Connect(org.eclipse.hawkbit.ui.customrenderers.renderers.GridButtonRenderer.class) +public class GridButtonRendererConnector extends ClickableRendererConnector { + private static final long serialVersionUID = 7987417436367399331L; + + @Override + public org.eclipse.hawkbit.ui.customrenderers.client.renderers.GridButtonRenderer getRenderer() { + return (org.eclipse.hawkbit.ui.customrenderers.client.renderers.GridButtonRenderer) super.getRenderer(); + } + + @Override + protected HandlerRegistration addClickHandler(final RendererClickHandler handler) { + return getRenderer().addClickHandler(handler); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/renderers/FontIconData.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/renderers/FontIconData.java new file mode 100644 index 000000000..cb4e0fa15 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/renderers/FontIconData.java @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.customrenderers.client.renderers; + +import java.io.Serializable; + +/** + * Data class that transports the icon meta-data to client-side renderer. + */ +public class FontIconData implements Serializable { + + /** serialVersionUID. */ + private static final long serialVersionUID = 5823318280700107049L; + private String fontIconHtml; + private String style; + private String title; + private String id; + private boolean disabled; + + /** + * Gets the html representing the icon. + * + * @return the html representing the icon + */ + public String getFontIconHtml() { + return fontIconHtml; + } + + /** + * Sets the html representing the icon. + * + * @param fontIconHtml + * html representing the icon + */ + public void setFontIconHtml(String fontIconHtml) { + this.fontIconHtml = fontIconHtml; + } + + /** + * Gets the style. + * + * @return the style + */ + public String getStyle() { + return style; + } + + /** + * Sets the style. + * + * @param style + * icon style + */ + public void setStyle(String style) { + this.style = style; + } + + /** + * Gets the title shown as tooltip. + * + * @return the title shown as tooltip. + */ + public String getTitle() { + return title; + } + + /** + * Sets the title shown as tooltip. + * + * @param title + * shown as tooltip. + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Gets the id for direct access. + * + * @return the id for direct access. + */ + public String getId() { + return id; + } + + /** + * Sets the id for direct access. + * + * @param id + * for direct access. + */ + public void setId(String id) { + this.id = id; + } + + /** + * Gets the disabled-state of the icon. + * + * @return the disabled-state of the icon. + */ + public boolean isDisabled() { + return disabled; + } + + /** + * Sets the disabled-state of the icon. + * + * @param disabled + * disabled-state of the icon. + */ + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/renderers/GridButtonRenderer.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/renderers/GridButtonRenderer.java new file mode 100644 index 000000000..ab69ccb90 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/renderers/GridButtonRenderer.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.customrenderers.client.renderers; + +import com.google.gwt.core.shared.GWT; +import com.google.gwt.user.client.ui.Button; +import com.vaadin.client.renderers.ClickableRenderer; +import com.vaadin.client.ui.VButton; +import com.vaadin.client.widget.grid.RendererCellReference; + +/** + * Renders button with provided HTML content. Used to display button with icons. + */ +public class GridButtonRenderer extends ClickableRenderer { + + @Override + public Button createWidget() { + Button b = GWT.create(Button.class); + b.addClickHandler(this); + b.setStylePrimaryName("v-nativebutton"); + return b; + } + + @Override + public void render(final RendererCellReference cell, final FontIconData iconMetadata, final Button button) { + if (iconMetadata.getFontIconHtml() != null) { + button.setHTML(iconMetadata.getFontIconHtml()); + } + applyStyles(button, iconMetadata.isDisabled(), iconMetadata.getStyle()); + button.getElement().setId(iconMetadata.getId()); + button.getElement().setTitle(iconMetadata.getTitle()); + button.setEnabled(!iconMetadata.isDisabled()); + // this is to allow the button to disappear, if the text is null + button.setVisible(iconMetadata.getFontIconHtml() != null); + } + + private static void applyStyles(final Button button, final boolean buttonDisabled, final String additionalStyle) { + + button.setStyleName(VButton.CLASSNAME); + button.addStyleName(getStyle("tiny")); + button.addStyleName(getStyle("borderless")); + button.addStyleName(getStyle("button-no-border")); + button.addStyleName(getStyle("action-type-padding")); + button.addStyleName(getStyle(additionalStyle)); + + if (buttonDisabled) { + button.addStyleName("v-disabled"); + } + } + + private static String getStyle(final String style) { + return new StringBuilder(style).append(" ").append(VButton.CLASSNAME).append("-").append(style).toString(); + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/renderers/HtmlLabelRenderer.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/renderers/HtmlLabelRenderer.java index c6b2d9b64..d3da6072c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/renderers/HtmlLabelRenderer.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/client/renderers/HtmlLabelRenderer.java @@ -17,7 +17,7 @@ import com.vaadin.client.ui.VLabel; import com.vaadin.client.widget.grid.RendererCellReference; /** - * + * * Renders label with provided value and style. * */ @@ -33,6 +33,7 @@ public class HtmlLabelRenderer extends WidgetRenderer { Map map = formatInput(input); String value = map.containsKey("value") ? map.get("value") : null; String style = map.containsKey("style") ? map.get("style") : null; + String title = map.containsKey("title") ? map.get("title") : null; String id = map.containsKey("id") ? map.get("id") : null; if (value != null) { @@ -42,6 +43,7 @@ public class HtmlLabelRenderer extends WidgetRenderer { } applyStyle(label, style); label.getElement().setId(id); + label.getElement().setTitle(title); } private void applyStyle(VLabel label, String style) { @@ -57,7 +59,7 @@ public class HtmlLabelRenderer extends WidgetRenderer { return new StringBuilder(style).append(" ").append(VLabel.CLASSNAME).append("-").append(style).toString(); } - private Map formatInput(String input) { + private static Map formatInput(String input) { Map details = new HashMap<>(); String[] tempData = input.split(","); for (String statusWithCount : tempData) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/AbstractGridButtonConverter.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/AbstractGridButtonConverter.java new file mode 100644 index 000000000..370355cf1 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/AbstractGridButtonConverter.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.customrenderers.renderers; + +import java.io.Serializable; +import java.util.Locale; + +import org.eclipse.hawkbit.ui.customrenderers.client.renderers.FontIconData; +import org.eclipse.hawkbit.ui.rollout.StatusFontIcon; + +import com.vaadin.data.util.converter.Converter; + +/** + * Converter that adapts to a model and converts to a grid-button presentation. + * + * @param + * The type the converter adapts to + */ +public abstract class AbstractGridButtonConverter implements Converter { + + private static final long serialVersionUID = 1L; + + private GridButtonAdapter adapter; + + @Override + public T convertToModel(final FontIconData meta, final Class targetType, final Locale locale) { + // not needed + return null; + } + + @Override + public FontIconData convertToPresentation(final T status, final Class targetType, + final Locale locale) { + if (adapter == null) { + throw new IllegalStateException( + "Adapter must be set before usage! Convertion without adapter is not possible!"); + } + return createFontIconData(adapter.adapt(status)); + } + + /** + * Creates a data transport object for the icon meta data. + * + * @param meta + * icon metadata + * @return icon metadata transport object + */ + private static FontIconData createFontIconData(StatusFontIcon meta) { + FontIconData result = new FontIconData(); + result.setFontIconHtml(meta.getFontIcon().getHtml()); + result.setTitle(meta.getTitle()); + result.setStyle(meta.getStyle()); + result.setId(meta.getId()); + result.setDisabled(meta.isDisabled()); + + return result; + } + + @Override + public Class getPresentationType() { + return FontIconData.class; + } + + /** + * Adds an appropriate adapter to the converter. The adapter is mandatory + * for the converter. + * + * @param adapter + * label-adapter that converts from model to label-presentation. + * @return self for method-chaining + */ + public AbstractGridButtonConverter addAdapter(GridButtonAdapter adapter) { + this.adapter = adapter; + return this; + } + + /** + * Adapts from model data to presentation data. + * + * @param + * The type to adapt to + */ + @FunctionalInterface + public interface GridButtonAdapter extends Serializable { + + /** + * @param status + * model data + * @return meta representation that is used as input for label + * generation. + */ + StatusFontIcon adapt(final T status); + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/AbstractHtmlLabelConverter.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/AbstractHtmlLabelConverter.java new file mode 100644 index 000000000..50af2d38c --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/AbstractHtmlLabelConverter.java @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.customrenderers.renderers; + +import java.io.Serializable; +import java.util.Locale; + +import org.eclipse.hawkbit.ui.rollout.StatusFontIcon; + +import com.google.common.base.Strings; +import com.vaadin.data.util.converter.Converter; + +/** + * Converter that adapts to a model and converts to a label presentation. + * + * @param + * The type the converter adapts to + */ +public abstract class AbstractHtmlLabelConverter implements Converter { + + private static final long serialVersionUID = 1L; + + private LabelAdapter adapter; + + @Override + public T convertToModel(final String value, final Class targetType, final Locale locale) { + // not needed + return null; + } + + @Override + public String convertToPresentation(final T status, final Class targetType, final Locale locale) { + return convert(status); + } + + @Override + public Class getPresentationType() { + return String.class; + } + + /** + * Adds an appropriate adapter to the converter. The adapter is mandatory + * for the converter. + * + * @param adapter + * label-adapter that converts from model to label-presentation. + * @return self for method-chaining + */ + public AbstractHtmlLabelConverter addAdapter(LabelAdapter adapter) { + this.adapter = adapter; + return this; + } + + /** + * Converts the model data to a string representation of the presentation + * label that is used on client-side to style the label. + * + * @param status + * model data + * @return string representation of label + */ + private String convert(final T status) { + if (adapter == null) { + throw new IllegalStateException( + "Adapter must be set before usage! Convertion without adapter is not possible!"); + } + final StatusFontIcon statusProps = adapter.adapt(status); + // fail fast + if (statusProps == null) { + return ""; + } + final String codePoint = getCodePoint(statusProps); + final String title = statusProps.getTitle(); + return getStatusLabelDetailsInString(codePoint, statusProps.getStyle(), title, statusProps.getId(), + statusProps.isDisabled()); + } + + /** + * Creates a key:value map represented by a comma-separated list. + * + * @param fontIcon + * the font representing the icon + * @param style + * the style + * @param title + * the title shown as tooltip + * @param id + * the id for direct access + * @param disabled + * disabled-state of the icon + * @return string representation of key:value map + */ + private static String getStatusLabelDetailsInString(final String value, final String style, final String title, + final String id, boolean disabled) { + final StringBuilder val = new StringBuilder(); + if (!Strings.isNullOrEmpty(value)) { + val.append("value:").append(value).append(","); + } + if (!Strings.isNullOrEmpty(style)) { + val.append("style:").append(style).append(","); + } + if (!Strings.isNullOrEmpty(title)) { + val.append("title:").append(title).append(","); + } + if (disabled) { + val.append("disabled:true").append(","); + } + return val.append("id:").append(id).toString(); + } + + /** + * Retrieves the codepoint from the font-icon. + * + * @param statusFontIcon + * the label-metadata that holds the font-icon + * + */ + private static String getCodePoint(final StatusFontIcon statusFontIcon) { + return statusFontIcon.getFontIcon() != null ? Integer.toString(statusFontIcon.getFontIcon().getCodepoint()) + : null; + } + + /** + * Adapts from model data to presentation data. + * + * @param + * The type to adapt to + */ + @FunctionalInterface + public interface LabelAdapter extends Serializable { + + /** + * @param status + * model data + * @return meta representation that is used as input for label + * generation. + */ + StatusFontIcon adapt(final T status); + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/GridButtonRenderer.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/GridButtonRenderer.java new file mode 100644 index 000000000..8cab5d3c9 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/GridButtonRenderer.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.customrenderers.renderers; + +import org.eclipse.hawkbit.ui.customrenderers.client.renderers.FontIconData; +import com.vaadin.ui.renderers.ClickableRenderer; +import elemental.json.JsonValue; + +/** + * Renders buttons for a grid with provided HTML content based on meta-data. + * Used to display button with icons. + */ +public class GridButtonRenderer extends ClickableRenderer { + + private static final long serialVersionUID = 1L; + + /** + * Constructor. + */ + public GridButtonRenderer() { + super(FontIconData.class, null); + } + + /** + * Creates a new custom object renderer and adds the given click listener to + * it. + * + * @param listener + * the click listener to register + */ + public GridButtonRenderer(final RendererClickListener listener) { + this(); + addClickListener(listener); + } + + + + /** + * Initialize custom object renderer with the given type. + * + * @param presentationType + * Class + */ + + public GridButtonRenderer(final Class presentationType) { + super(presentationType); + } + + + @Override + public JsonValue encode(final FontIconData resource) { + return super.encode(resource, FontIconData.class); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/HtmlLabelRenderer.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/HtmlLabelRenderer.java index 0acc05177..6a688cbdb 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/HtmlLabelRenderer.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/customrenderers/renderers/HtmlLabelRenderer.java @@ -11,7 +11,7 @@ package org.eclipse.hawkbit.ui.customrenderers.renderers; import com.vaadin.ui.Grid.AbstractRenderer; /** - * + * * Renders label with provided value and style. * */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterHeader.java index ded682632..90d48a685 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterHeader.java @@ -154,7 +154,7 @@ public class TargetFilterHeader extends VerticalLayout { private void openSearchTextField() { searchResetIcon.addStyleName(SPUIDefinitions.FILTER_RESET_ICON); - searchResetIcon.togleIcon(FontAwesome.TIMES); + searchResetIcon.toggleIcon(FontAwesome.TIMES); searchResetIcon.setData(Boolean.TRUE); searchField.removeStyleName(SPUIDefinitions.FILTER_BOX_HIDE); searchField.setVisible(true); @@ -165,7 +165,7 @@ public class TargetFilterHeader extends VerticalLayout { searchField.setValue(""); searchField.addStyleName(SPUIDefinitions.FILTER_BOX_HIDE); searchResetIcon.removeStyleName(SPUIDefinitions.FILTER_RESET_ICON); - searchResetIcon.togleIcon(FontAwesome.SEARCH); + searchResetIcon.toggleIcon(FontAwesome.SEARCH); searchResetIcon.setData(Boolean.FALSE); resetSearchText(); 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 4b4becc17..e512010f1 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 @@ -28,7 +28,11 @@ import org.eclipse.hawkbit.ui.components.AbstractNotificationView; import org.eclipse.hawkbit.ui.components.NotificationUnreadButton; import org.eclipse.hawkbit.ui.components.RefreshableContainer; import org.eclipse.hawkbit.ui.dd.criteria.ManagementViewClientCriterion; -import org.eclipse.hawkbit.ui.management.actionhistory.ActionHistoryComponent; +import org.eclipse.hawkbit.ui.management.actionhistory.ActionHistoryLayout; +import org.eclipse.hawkbit.ui.management.actionhistory.ActionStatusGrid; +import org.eclipse.hawkbit.ui.management.actionhistory.ActionStatusLayout; +import org.eclipse.hawkbit.ui.management.actionhistory.ActionStatusMsgGrid; +import org.eclipse.hawkbit.ui.management.actionhistory.ActionStatusMsgLayout; import org.eclipse.hawkbit.ui.management.dstable.DistributionTableLayout; import org.eclipse.hawkbit.ui.management.dstag.DistributionTagLayout; import org.eclipse.hawkbit.ui.management.event.DistributionTableEvent; @@ -89,7 +93,11 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW private final ManagementUIState managementUIState; - private final ActionHistoryComponent actionHistoryComponent; + private final ActionHistoryLayout actionHistoryLayout; + + private final ActionStatusLayout actionStatusLayout; + + private final ActionStatusMsgLayout actionStatusMsgLayout; private final TargetTagFilterLayout targetTagFilterLayout; @@ -97,7 +105,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW private final DistributionTagLayout distributionTagLayout; - private final DistributionTableLayout distributionTableLayoutNew; + private final DistributionTableLayout distributionTableLayout; private final DeleteActionsLayout deleteAndActionsLayout; @@ -121,8 +129,10 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW this.i18n = i18n; this.uiNotification = uiNotification; this.managementUIState = managementUIState; - this.actionHistoryComponent = new ActionHistoryComponent(i18n, deploymentManagement, eventBus, uiNotification, + this.actionHistoryLayout = new ActionHistoryLayout(i18n, deploymentManagement, eventBus, uiNotification, managementUIState); + this.actionStatusLayout = new ActionStatusLayout(i18n, eventBus, managementUIState); + this.actionStatusMsgLayout = new ActionStatusMsgLayout(i18n, eventBus, managementUIState); final CreateUpdateTargetTagLayoutWindow createUpdateTargetTagLayout = new CreateUpdateTargetTagLayoutWindow( i18n, tagManagement, entityFactory, eventBus, permChecker, uiNotification); this.targetTagFilterLayout = new TargetTagFilterLayout(i18n, createUpdateTargetTagLayout, managementUIState, @@ -139,7 +149,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW this.distributionTagLayout = new DistributionTagLayout(eventbus, managementUIState, i18n, permChecker, eventBus, tagManagement, entityFactory, uiNotification, distFilterParameters, distributionSetManagement, managementViewClientCriterion); - this.distributionTableLayoutNew = new DistributionTableLayout(i18n, eventBus, permChecker, managementUIState, + this.distributionTableLayout = new DistributionTableLayout(i18n, eventBus, permChecker, managementUIState, distributionSetManagement, managementViewClientCriterion, entityFactory, uiNotification, tagManagement, systemManagement, targetManagement, deploymentManagement); this.deleteAndActionsLayout = new DeleteActionsLayout(i18n, permChecker, eventBus, uiNotification, @@ -147,6 +157,9 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW deploymentManagement, distributionSetManagement); this.deploymentViewMenuItem = deploymentViewMenuItem; + + actionHistoryLayout.registerDetails(((ActionStatusGrid) actionStatusLayout.getGrid()).getDetailsSupport()); + actionStatusLayout.registerDetails(((ActionStatusMsgGrid) actionStatusMsgLayout.getGrid()).getDetailsSupport()); } @PostConstruct @@ -238,11 +251,13 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW mainLayout.setRows(2); mainLayout.addComponent(targetTagFilterLayout, 0, 0); mainLayout.addComponent(targetTableLayout, 1, 0); - mainLayout.addComponent(actionHistoryComponent, 4, 0); - mainLayout.addComponent(distributionTableLayoutNew, 2, 0); + mainLayout.addComponent(distributionTableLayout, 2, 0); mainLayout.addComponent(distributionTagLayout, 3, 0); + mainLayout.addComponent(actionHistoryLayout, 4, 0); + mainLayout.setColumnExpandRatio(0, 0F); mainLayout.setColumnExpandRatio(1, 0.275F); mainLayout.setColumnExpandRatio(2, 0.275F); + mainLayout.setColumnExpandRatio(3, 0F); mainLayout.setColumnExpandRatio(4, 0.45F); if (showFooterLayout()) { mainLayout.addComponent(deleteAndActionsLayout, 1, 1, 2, 1); @@ -253,7 +268,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW private void displayDistributionWidgetsOnly() { mainLayout.setColumns(2); mainLayout.setRows(2); - mainLayout.addComponent(distributionTableLayoutNew, 0, 0); + mainLayout.addComponent(distributionTableLayout, 0, 0); mainLayout.addComponent(distributionTagLayout, 1, 0); mainLayout.setColumnExpandRatio(0, 1F); if (showFooterLayout()) { @@ -280,7 +295,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW mainLayout.setRows(2); mainLayout.addComponent(targetTagFilterLayout, 0, 0); mainLayout.addComponent(targetTableLayout, 1, 0); - mainLayout.addComponent(actionHistoryComponent, 2, 0); + mainLayout.addComponent(actionHistoryLayout, 2, 0); mainLayout.setColumnExpandRatio(1, 0.4F); mainLayout.setColumnExpandRatio(2, 0.6F); if (showFooterLayout()) { @@ -291,10 +306,10 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW private void maximizeTargetTable() { if (permChecker.hasReadDistributionPermission()) { - mainLayout.removeComponent(distributionTableLayoutNew); + mainLayout.removeComponent(distributionTableLayout); mainLayout.removeComponent(distributionTagLayout); } - mainLayout.removeComponent(actionHistoryComponent); + mainLayout.removeComponent(actionHistoryLayout); mainLayout.removeComponent(deleteAndActionsLayout); mainLayout.setColumnExpandRatio(1, 1F); mainLayout.setColumnExpandRatio(2, 0F); @@ -306,7 +321,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW if (permChecker.hasTargetReadPermission()) { mainLayout.removeComponent(targetTagFilterLayout); mainLayout.removeComponent(targetTableLayout); - mainLayout.removeComponent(actionHistoryComponent); + mainLayout.removeComponent(actionHistoryLayout); } mainLayout.removeComponent(deleteAndActionsLayout); mainLayout.setColumnExpandRatio(0, 0F); @@ -316,20 +331,16 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW } private void maximizeActionHistory() { - mainLayout.setSpacing(false); - mainLayout.removeComponent(targetTagFilterLayout); - mainLayout.removeComponent(targetTableLayout); - if (permChecker.hasReadDistributionPermission()) { - mainLayout.removeComponent(distributionTableLayoutNew); - mainLayout.removeComponent(distributionTagLayout); - } - mainLayout.setColumnExpandRatio(0, 0F); - mainLayout.setColumnExpandRatio(1, 0F); - mainLayout.setColumnExpandRatio(2, 0F); - mainLayout.setColumnExpandRatio(3, 0F); - mainLayout.setColumnExpandRatio(4, 1F); - mainLayout.removeComponent(deleteAndActionsLayout); - mainLayout.setComponentAlignment(actionHistoryComponent, Alignment.TOP_LEFT); + mainLayout.removeAllComponents(); + mainLayout.setColumns(3); + mainLayout.setRows(1); + mainLayout.addComponent(actionHistoryLayout, 0, 0); + mainLayout.addComponent(actionStatusLayout, 1, 0); + mainLayout.addComponent(actionStatusMsgLayout, 2, 0); + mainLayout.setColumnExpandRatio(0, 0.55F); + mainLayout.setColumnExpandRatio(1, 0.18F); + mainLayout.setColumnExpandRatio(2, 0.27F); + mainLayout.setComponentAlignment(actionHistoryLayout, Alignment.TOP_LEFT); } private void minimizeTargetTable() { @@ -342,7 +353,6 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW private void minimizeActionHistory() { layoutWidgets(); - mainLayout.setSpacing(true); } private void checkNoDataAvaialble() { @@ -362,7 +372,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW targetTagFilterLayout.setVisible(false); targetTableLayout.setShowFilterButtonVisible(true); distributionTagLayout.setVisible(false); - distributionTableLayoutNew.setShowFilterButtonVisible(true); + distributionTableLayout.setShowFilterButtonVisible(true); } else { if (!managementUIState.isTargetTagFilterClosed()) { targetTagFilterLayout.setVisible(true); @@ -371,7 +381,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW } if (!managementUIState.isDistTagFilterClosed()) { distributionTagLayout.setVisible(true); - distributionTableLayoutNew.setShowFilterButtonVisible(false); + distributionTableLayout.setShowFilterButtonVisible(false); } } } @@ -383,8 +393,8 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW supportedEvents.put(TargetCreatedEventContainer.class, targetTableLayout.getTable()); supportedEvents.put(TargetDeletedEventContainer.class, targetTableLayout.getTable()); - supportedEvents.put(DistributionCreatedEventContainer.class, distributionTableLayoutNew.getTable()); - supportedEvents.put(DistributionDeletedEventContainer.class, distributionTableLayoutNew.getTable()); + supportedEvents.put(DistributionCreatedEventContainer.class, distributionTableLayout.getTable()); + supportedEvents.put(DistributionDeletedEventContainer.class, distributionTableLayout.getTable()); supportedEvents.put(TargetTagCreatedEventContainer.class, targetTagFilterLayout); supportedEvents.put(TargetTagDeletedEventContainer.class, targetTagFilterLayout); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionBeanQuery.java new file mode 100644 index 000000000..b5a22eb59 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionBeanQuery.java @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import static org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil.isNotNullOrEmpty; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.ArrayUtils; +import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.ui.management.actionhistory.ProxyAction.IsActiveDecoration; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SpringContextHelper; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery; +import org.vaadin.addons.lazyquerycontainer.QueryDefinition; + +/** + * Simple implementation of generics bean query which dynamically loads + * {@link ProxyAction} batch of beans. + * + */ +public class ActionBeanQuery extends AbstractBeanQuery { + private static final long serialVersionUID = 3596912494728552516L; + + private Sort sort = new Sort(Direction.DESC, ProxyAction.PXY_ACTION_ID); + private transient DeploymentManagement deploymentManagement; + + private String currentSelectedConrollerId; + private transient Slice firstPageActions; + + /** + * Parametric Constructor. + * + * @param definition + * QueryDefinition contains the query properties. + * @param queryConfig + * Implementation specific configuration. + * @param sortPropertyIds + * The properties participating in sort. + * @param sortStates + * The ascending or descending state of sort properties. + */ + public ActionBeanQuery(final QueryDefinition definition, final Map queryConfig, + final Object[] sortPropertyIds, final boolean[] sortStates) { + super(definition, queryConfig, sortPropertyIds, sortStates); + + if (isNotNullOrEmpty(queryConfig)) { + currentSelectedConrollerId = (String) queryConfig.get(SPUIDefinitions.ACTIONS_BY_TARGET); + } + + if (ArrayUtils.isEmpty(sortStates)) { + return; + } + + // Initialize sort + sort = new Sort(sortStates[0] ? Direction.ASC : Direction.DESC, (String) sortPropertyIds[0]); + // Add sort + for (int distId = 1; distId < sortPropertyIds.length; distId++) { + sort.and(new Sort(sortStates[distId] ? Direction.ASC : Direction.DESC, (String) sortPropertyIds[distId])); + } + } + + @Override + protected ProxyAction constructBean() { + return new ProxyAction(); + } + + @Override + protected List loadBeans(final int startIndex, final int count) { + Slice actionBeans; + if (startIndex == 0) { + if (firstPageActions == null) { + firstPageActions = getDeploymentManagement().findActionsByTarget(currentSelectedConrollerId, + new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort)); + } + actionBeans = firstPageActions; + } else { + actionBeans = getDeploymentManagement().findActionsByTarget(currentSelectedConrollerId, + new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, SPUIDefinitions.PAGE_SIZE, sort)); + } + return createProxyActions(actionBeans); + } + + /** + * Creates a list of {@link ProxyAction}s for presentation layer from slice + * of {@link Action}s. + * + * @param actionBeans + * slice of {@link Action}s + * @return list of {@link ProxyAction}s + */ + private static List createProxyActions(final Slice actionBeans) { + final List proxyActions = new ArrayList<>(); + for (final Action action : actionBeans) { + final ProxyAction proxyAction = new ProxyAction(); + final String dsNameVersion = action.getDistributionSet().getName() + ":" + + action.getDistributionSet().getVersion(); + proxyAction.setActive(action.isActive()); + proxyAction.setIsActiveDecoration(buildIsActiveDecoration(action)); + proxyAction.setDsNameVersion(dsNameVersion); + proxyAction.setAction(action); + proxyAction.setId(action.getId()); + proxyAction.setLastModifiedAt(action.getLastModifiedAt()); + proxyAction.setRolloutName(action.getRollout() != null ? action.getRollout().getName() : ""); + proxyAction.setStatus(action.getStatus()); + + proxyActions.add(proxyAction); + } + return proxyActions; + } + + /** + * Generates a virtual IsActiveDecoration for the presentation layer that is + * calculated from {@link Action#isActive()} and + * {@link Action#getActionStatus()}. + * + * @param action + * the action combined IsActiveDecoration is calculated from + * @return IsActiveDecoration combined decoration for the presentation + * layer. + */ + private static IsActiveDecoration buildIsActiveDecoration(final Action action) { + final Action.Status status = action.getStatus(); + + if (status == Action.Status.SCHEDULED) { + return IsActiveDecoration.SCHEDULED; + } else if (status == Action.Status.ERROR) { + return IsActiveDecoration.IN_ACTIVE_ERROR; + } + + return action.isActive() ? IsActiveDecoration.ACTIVE : IsActiveDecoration.IN_ACTIVE; + } + + @Override + protected void saveBeans(final List addedBeans, final List modifiedBeans, + final List removedBeans) { + // CRUD operations on Target will be done through repository methods + } + + @Override + public int size() { + long size = 0; + + if (currentSelectedConrollerId != null) { + size = getDeploymentManagement().countActionsByTarget(currentSelectedConrollerId); + } + if (size > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + + return (int) size; + } + + /** + * Lazy gets deploymentManagement. + * + * @return the deploymentManagement + */ + public DeploymentManagement getDeploymentManagement() { + if (null == deploymentManagement) { + deploymentManagement = SpringContextHelper.getBean(DeploymentManagement.class); + } + return deploymentManagement; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryComponent.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryComponent.java deleted file mode 100644 index 2205f3eca..000000000 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryComponent.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; - -import org.eclipse.hawkbit.repository.DeploymentManagement; -import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.ui.common.table.BaseEntityEventType; -import org.eclipse.hawkbit.ui.management.event.TargetTableEvent; -import org.eclipse.hawkbit.ui.management.state.ManagementUIState; -import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; -import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; -import org.eclipse.hawkbit.ui.utils.UINotification; -import org.vaadin.spring.events.EventBus.UIEventBus; -import org.vaadin.spring.events.EventScope; -import org.vaadin.spring.events.annotation.EventBusListenerMethod; - -import com.vaadin.ui.Alignment; -import com.vaadin.ui.UI; -import com.vaadin.ui.VerticalLayout; - -/** - * - * - */ -public class ActionHistoryComponent extends VerticalLayout { - private static final long serialVersionUID = -3766179797384539821L; - - private final ActionHistoryHeader actionHistoryHeader; - private final ActionHistoryTable actionHistoryTable; - - public ActionHistoryComponent(final VaadinMessageSource i18n, final DeploymentManagement deploymentManagement, - final UIEventBus eventBus, final UINotification notification, final ManagementUIState managementUIState) { - this.actionHistoryHeader = new ActionHistoryHeader(eventBus, managementUIState); - this.actionHistoryTable = new ActionHistoryTable(i18n, deploymentManagement, eventBus, notification, - managementUIState); - buildLayout(); - setSizeFull(); - setImmediate(true); - eventBus.subscribe(this); - } - - @EventBusListenerMethod(scope = EventScope.UI) - void onEvent(final TargetTableEvent targetUIEvent) { - if (BaseEntityEventType.SELECTED_ENTITY == targetUIEvent.getEventType()) { - setData(SPUIDefinitions.DATA_AVAILABLE); - UI.getCurrent().access(() -> populateActionHistoryDetails(targetUIEvent.getEntity())); - } - } - - private void buildLayout() { - setSizeFull(); - setSpacing(false); - setMargin(false); - addStyleName("table-layout"); - addComponents(actionHistoryHeader, actionHistoryTable); - setComponentAlignment(actionHistoryHeader, Alignment.TOP_CENTER); - setComponentAlignment(actionHistoryTable, Alignment.TOP_CENTER); - setExpandRatio(actionHistoryTable, 1.0F); - } - - /** - * populate action header and table for the target. - * - * @param target - * the target - */ - public void populateActionHistoryDetails(final Target target) { - if (null != target) { - actionHistoryHeader.populateHeader(target.getName()); - actionHistoryTable.setAlreadyHasMessages(false); - actionHistoryTable.populateTableData(target); - } else { - actionHistoryHeader.updateActionHistoryHeader(" "); - actionHistoryTable.setAlreadyHasMessages(false); - actionHistoryTable.clearContainerData(); - } - - } -} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryGrid.java new file mode 100644 index 000000000..c67375646 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryGrid.java @@ -0,0 +1,792 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.ui.common.ConfirmationDialog; +import org.eclipse.hawkbit.ui.common.grid.AbstractGrid; +import org.eclipse.hawkbit.ui.common.table.BaseEntityEventType; +import org.eclipse.hawkbit.ui.customrenderers.renderers.AbstractGridButtonConverter; +import org.eclipse.hawkbit.ui.customrenderers.renderers.AbstractHtmlLabelConverter; +import org.eclipse.hawkbit.ui.customrenderers.renderers.GridButtonRenderer; +import org.eclipse.hawkbit.ui.customrenderers.renderers.HtmlLabelRenderer; +import org.eclipse.hawkbit.ui.management.actionhistory.ProxyAction.IsActiveDecoration; +import org.eclipse.hawkbit.ui.management.event.ManagementUIEvent; +import org.eclipse.hawkbit.ui.management.event.PinUnpinEvent; +import org.eclipse.hawkbit.ui.management.event.TargetTableEvent; +import org.eclipse.hawkbit.ui.management.state.ManagementUIState; +import org.eclipse.hawkbit.ui.rollout.StatusFontIcon; +import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UINotification; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; +import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; +import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition; +import org.vaadin.spring.events.EventBus.UIEventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.google.common.collect.Maps; +import com.vaadin.data.Item; +import com.vaadin.data.util.GeneratedPropertyContainer; +import com.vaadin.data.util.PropertyValueGenerator; +import com.vaadin.server.FontAwesome; +import com.vaadin.ui.UI; + +/** + * This grid presents the action history for a selected target. + */ +public class ActionHistoryGrid extends AbstractGrid { + private static final long serialVersionUID = 4324796883957831443L; + + private static final Logger LOG = LoggerFactory.getLogger(ActionHistoryGrid.class); + private static final String BUTTON_CANCEL = "button.cancel"; + private static final String BUTTON_OK = "button.ok"; + private static final double FIXED_PIX_MIN = 29F; + private static final double FIXED_PIX_MAX = 32F; + + private static final String STATUS_ICON_GREEN = "statusIconGreen"; + private static final String STATUS_ICON_RED = "statusIconRed"; + private static final String STATUS_ICON_ORANGE = "statusIconOrange"; + private static final String STATUS_ICON_PENDING = "statusIconPending"; + private static final String STATUS_ICON_NEUTRAL = "statusIconNeutral"; + private static final String STATUS_ICON_ACTIVE = "statusIconActive"; + private static final String STATUS_ICON_FORCED = "statusIconForced"; + + private static final String VIRT_PROP_FORCED = "forced"; + private static final String VIRT_PROP_TIMEFORCED = "timeForced"; + private static final String VIRT_PROP_ACTION_CANCEL = "cancel-aciton"; + private static final String VIRT_PROP_ACTION_FORCE = "force-action"; + private static final String VIRT_PROP_ACTION_FORCE_QUIT = "force-quit-action"; + + private static final Object[] maxColumnOrder = new Object[] { ProxyAction.PXY_ACTION_IS_ACTIVE_DECO, + ProxyAction.PXY_ACTION_ID, ProxyAction.PXY_ACTION_DS_NAME_VERSION, ProxyAction.PXY_ACTION_LAST_MODIFIED_AT, + ProxyAction.PXY_ACTION_STATUS, ProxyAction.PXY_ACTION_ROLLOUT_NAME, VIRT_PROP_FORCED, VIRT_PROP_TIMEFORCED, + VIRT_PROP_ACTION_CANCEL, VIRT_PROP_ACTION_FORCE, VIRT_PROP_ACTION_FORCE_QUIT }; + + private static final Object[] minColumnOrder = new Object[] { ProxyAction.PXY_ACTION_IS_ACTIVE_DECO, + ProxyAction.PXY_ACTION_DS_NAME_VERSION, ProxyAction.PXY_ACTION_LAST_MODIFIED_AT, + ProxyAction.PXY_ACTION_STATUS, VIRT_PROP_FORCED, VIRT_PROP_TIMEFORCED, VIRT_PROP_ACTION_CANCEL, + VIRT_PROP_ACTION_FORCE, VIRT_PROP_ACTION_FORCE_QUIT }; + + private static final String[] leftAlignedColumns = new String[] { VIRT_PROP_TIMEFORCED }; + + private static final String[] centerAlignedColumns = new String[] { ProxyAction.PXY_ACTION_IS_ACTIVE_DECO, + ProxyAction.PXY_ACTION_STATUS }; + + private static final String[] rightAlignedColumns = new String[] { VIRT_PROP_FORCED, ProxyAction.PXY_ACTION_ID }; + + private final transient DeploymentManagement deploymentManagement; + private final UINotification notification; + private final ManagementUIState managementUIState; + + private Target selectedTarget; + private final AlignCellStyleGenerator alignGenerator; + private final ModifiedTimeTooltipGenerator modTimetooltipGenerator; + + private final Map states; + private final Map activeStates; + + private final BeanQueryFactory targetQF = new BeanQueryFactory<>(ActionBeanQuery.class); + + boolean forceClientRefreshToggle = true; + + /** + * Constructor. + * + * @param i18n + * @param deploymentManagement + * @param eventBus + * @param notification + * @param managementUIState + */ + protected ActionHistoryGrid(final VaadinMessageSource i18n, final DeploymentManagement deploymentManagement, + final UIEventBus eventBus, final UINotification notification, final ManagementUIState managementUIState) { + super(i18n, eventBus, null); + this.deploymentManagement = deploymentManagement; + this.notification = notification; + this.managementUIState = managementUIState; + + setMaximizeSupport(new ActionHistoryMaximizeSupport()); + setSingleSelectionSupport(new SingleSelectionSupport()); + + if (!managementUIState.isActionHistoryMaximized()) { + getSingleSelectionSupport().disable(); + } + + setGeneratedPropertySupport(new ActionHistoryGeneratedPropertySupport()); + setDetailsSupport(new DetailsSupport()); + + final LabelConfig conf = new LabelConfig(); + states = conf.createStatusLabelConfig(i18n, UIComponentIdProvider.ACTION_HISTORY_TABLE_STATUS_LABEL_ID); + activeStates = conf + .createActiveStatusLabelConfig(UIComponentIdProvider.ACTION_HISTORY_TABLE_ACTIVESTATE_LABEL_ID); + alignGenerator = new AlignCellStyleGenerator(leftAlignedColumns, centerAlignedColumns, rightAlignedColumns); + modTimetooltipGenerator = new ModifiedTimeTooltipGenerator(ProxyAction.PXY_ACTION_LAST_MODIFIED_AT); + + init(); + } + + @EventBusListenerMethod(scope = EventScope.UI) + void onEvent(final ManagementUIEvent mgmtUIEvent) { + if (mgmtUIEvent == ManagementUIEvent.MAX_ACTION_HISTORY) { + UI.getCurrent().access(this::createMaximizedContent); + } + if (mgmtUIEvent == ManagementUIEvent.MIN_ACTION_HISTORY) { + UI.getCurrent().access(this::createMinimizedContent); + } + } + + @Override + protected void init() { + super.init(); + restorePreviousState(); + } + + /** + * Set target as member of this grid (as all presented grid-data is related + * to this target) and recalculate grid-data for this target. + * + * @param selectedTarget + * reference of target + */ + public void populateSelectedTarget(final Target selectedTarget) { + this.selectedTarget = selectedTarget; + getDetailsSupport() + .populateMasterDataAndRecalculateContainer(selectedTarget != null ? selectedTarget.getId() : null); + } + + @Override + protected LazyQueryContainer createContainer() { + configureQueryFactory(); + return new LazyQueryContainer( + new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, ProxyAction.PXY_ACTION_ID), targetQF); + } + + @Override + public void refreshContainer() { + configureQueryFactory(); + super.refreshContainer(); + } + + protected void configureQueryFactory() { + // ADD all the filters to the query config + final Map queryConfig = Maps.newHashMapWithExpectedSize(1); + queryConfig.put(SPUIDefinitions.ACTIONS_BY_TARGET, + selectedTarget != null ? selectedTarget.getControllerId() : null); + // Create ActionBeanQuery factory with the query config. + targetQF.setQueryConfiguration(queryConfig); + } + + @Override + protected void addContainerProperties() { + final LazyQueryContainer rawCont = getGeneratedPropertySupport().getRawContainer(); + + rawCont.addContainerProperty(ProxyAction.PXY_ACTION_IS_ACTIVE_DECO, IsActiveDecoration.class, null, true, + false); + rawCont.addContainerProperty(ProxyAction.PXY_ACTION, Action.class, null, true, false); + rawCont.addContainerProperty(ProxyAction.PXY_ACTION_DS_NAME_VERSION, String.class, null, true, false); + rawCont.addContainerProperty(ProxyAction.PXY_ACTION_LAST_MODIFIED_AT, Long.class, null, true, true); + rawCont.addContainerProperty(ProxyAction.PXY_ACTION_STATUS, Action.Status.class, null, true, false); + + rawCont.addContainerProperty(ProxyAction.PXY_ACTION_ID, Long.class, null, true, true); + rawCont.addContainerProperty(ProxyAction.PXY_ACTION_ROLLOUT_NAME, String.class, null, true, true); + + } + + @Override + protected String getGridId() { + return UIComponentIdProvider.ACTION_HISTORY_GRID_ID; + } + + @Override + protected void addColumnRenderes() { + getColumn(ProxyAction.PXY_ACTION_LAST_MODIFIED_AT).setConverter(new LongToFormattedDateStringConverter()); + getColumn(ProxyAction.PXY_ACTION_STATUS).setRenderer(new HtmlLabelRenderer(), + new HtmlStatusLabelConverter(this::createStatusLabelMetadata)); + getColumn(ProxyAction.PXY_ACTION_IS_ACTIVE_DECO).setRenderer(new HtmlLabelRenderer(), + new HtmlIsActiveLabelConverter(this::createIsActiveLabelMetadata)); + getColumn(VIRT_PROP_FORCED).setRenderer(new HtmlLabelRenderer(), + new HtmlVirtPropLabelConverter(ActionHistoryGrid::createForcedLabelMetadata)); + getColumn(VIRT_PROP_TIMEFORCED).setRenderer(new HtmlLabelRenderer(), + new HtmlVirtPropLabelConverter(this::createTimeForcedLabelMetadata)); + getColumn(VIRT_PROP_ACTION_CANCEL).setRenderer( + new GridButtonRenderer(clickEvent -> confirmAndCancelAction((Long) clickEvent.getItemId())), + new ActionGridButtonConverter(this::createCancelButtonMetadata)); + getColumn(VIRT_PROP_ACTION_FORCE).setRenderer( + new GridButtonRenderer(clickEvent -> confirmAndForceAction((Long) clickEvent.getItemId())), + new ActionGridButtonConverter(this::createForceButtonMetadata)); + getColumn(VIRT_PROP_ACTION_FORCE_QUIT).setRenderer( + new GridButtonRenderer(clickEvent -> confirmAndForceQuitAction((Long) clickEvent.getItemId())), + new ActionGridButtonConverter(this::createForceQuitButtonMetadata)); + } + + private StatusFontIcon createCancelButtonMetadata(final Action action) { + final boolean isDisabled = !action.isActive() || action.isCancelingOrCanceled(); + return new StatusFontIcon(FontAwesome.TIMES, STATUS_ICON_NEUTRAL, i18n.getMessage("message.cancel.action"), + UIComponentIdProvider.ACTION_HISTORY_TABLE_CANCEL_ID, isDisabled); + } + + private StatusFontIcon createForceButtonMetadata(final Action action) { + final boolean isDisabled = !action.isActive() || action.isForce() || action.isCancelingOrCanceled(); + return new StatusFontIcon(FontAwesome.BOLT, STATUS_ICON_NEUTRAL, i18n.getMessage("message.force.action"), + UIComponentIdProvider.ACTION_HISTORY_TABLE_FORCE_ID, isDisabled); + } + + private StatusFontIcon createForceQuitButtonMetadata(final Action action) { + final boolean isDisabled = !action.isActive() || !action.isCancelingOrCanceled(); + return new StatusFontIcon(FontAwesome.TIMES, STATUS_ICON_RED, i18n.getMessage("message.forcequit.action"), + UIComponentIdProvider.ACTION_HISTORY_TABLE_FORCE_QUIT_ID, isDisabled); + } + + private StatusFontIcon createStatusLabelMetadata(final Action.Status status) { + return states.get(status); + } + + private StatusFontIcon createIsActiveLabelMetadata(final IsActiveDecoration isActiveDeco) { + return activeStates.get(isActiveDeco); + } + + private static StatusFontIcon createForcedLabelMetadata(final Action action) { + StatusFontIcon result = null; + if (ActionType.FORCED.equals(action.getActionType()) || ActionType.TIMEFORCED.equals(action.getActionType())) { + result = new StatusFontIcon(FontAwesome.BOLT, STATUS_ICON_FORCED, "Forced", + UIComponentIdProvider.ACTION_HISTORY_TABLE_FORCED_LABEL_ID); + } + return result; + } + + private StatusFontIcon createTimeForcedLabelMetadata(final Action action) { + StatusFontIcon result = null; + + if (ActionType.TIMEFORCED.equals(action.getActionType())) { + final long currentTimeMillis = System.currentTimeMillis(); + String style; + String title; + if (action.isHitAutoForceTime(currentTimeMillis)) { + style = STATUS_ICON_GREEN; + title = "auto forced since " + + SPDateTimeUtil.getDurationFormattedString(action.getForcedTime(), currentTimeMillis, i18n); + } else { + style = STATUS_ICON_PENDING; + title = "auto forcing in " + + SPDateTimeUtil.getDurationFormattedString(currentTimeMillis, action.getForcedTime(), i18n); + } + result = new StatusFontIcon(FontAwesome.HISTORY, style, title, + UIComponentIdProvider.ACTION_HISTORY_TABLE_TIMEFORCED_LABEL_ID); + } + return result; + } + + /** + * Show confirmation window and if ok then only, force the action. + * + * @param actionId + * as Id if the action needs to be forced. + */ + private void confirmAndForceAction(final Long actionId) { + /* Display the confirmation */ + final ConfirmationDialog confirmDialog = new ConfirmationDialog( + i18n.getMessage("caption.force.action.confirmbox"), i18n.getMessage("message.force.action.confirm"), + i18n.getMessage(BUTTON_OK), i18n.getMessage(BUTTON_CANCEL), ok -> { + if (!ok) { + return; + } + deploymentManagement.forceTargetAction(actionId); + populateAndUpdateTargetDetails(selectedTarget); + notification.displaySuccess(i18n.getMessage("message.force.action.success")); + }); + UI.getCurrent().addWindow(confirmDialog.getWindow()); + + confirmDialog.getWindow().bringToFront(); + } + + /** + * Show confirmation window and if ok then only, force quit action. + * + * @param actionId + * as Id if the action needs to be forced. + */ + private void confirmAndForceQuitAction(final Long actionId) { + /* Display the confirmation */ + final ConfirmationDialog confirmDialog = new ConfirmationDialog( + i18n.getMessage("caption.forcequit.action.confirmbox"), + i18n.getMessage("message.forcequit.action.confirm"), i18n.getMessage(BUTTON_OK), + i18n.getMessage(BUTTON_CANCEL), ok -> { + if (!ok) { + return; + } + final boolean cancelResult = forceQuitActiveAction(actionId); + if (cancelResult) { + populateAndUpdateTargetDetails(selectedTarget); + notification.displaySuccess(i18n.getMessage("message.forcequit.action.success")); + } else { + notification.displayValidationError(i18n.getMessage("message.forcequit.action.failed")); + } + }, FontAwesome.WARNING); + UI.getCurrent().addWindow(confirmDialog.getWindow()); + + confirmDialog.getWindow().bringToFront(); + } + + /** + * Show confirmation window and if ok then only, cancel the action. + * + * @param actionId + * as Id if the action needs to be cancelled. + */ + private void confirmAndCancelAction(final Long actionId) { + if (actionId == null) { + return; + } + + final ConfirmationDialog confirmDialog = new ConfirmationDialog( + i18n.getMessage("caption.cancel.action.confirmbox"), i18n.getMessage("message.cancel.action.confirm"), + i18n.getMessage(BUTTON_OK), i18n.getMessage(BUTTON_CANCEL), ok -> { + if (!ok) { + return; + } + final boolean cancelResult = cancelActiveAction(actionId); + if (cancelResult) { + populateAndUpdateTargetDetails(selectedTarget); + notification.displaySuccess(i18n.getMessage("message.cancel.action.success")); + } else { + notification.displayValidationError(i18n.getMessage("message.cancel.action.failed")); + } + }); + UI.getCurrent().addWindow(confirmDialog.getWindow()); + confirmDialog.getWindow().bringToFront(); + } + + private void populateAndUpdateTargetDetails(final Target target) { + // show the updated target action history details + populateSelectedTarget(target); + // update the target table and its pinning details + updateTargetAndDsTable(); + } + + private void updateTargetAndDsTable() { + eventBus.publish(this, new TargetTableEvent(BaseEntityEventType.UPDATED_ENTITY, selectedTarget)); + updateDistributionTableStyle(); + } + + /** + * Update the colors of Assigned and installed distribution set in Target + * Pinning. + */ + private void updateDistributionTableStyle() { + managementUIState.getDistributionTableFilters().getPinnedTarget().ifPresent((pinnedTarget) -> { + if (pinnedTarget.getTargetId().equals(selectedTarget.getId())) { + eventBus.publish(this, PinUnpinEvent.PIN_TARGET); + } + }); + if (!managementUIState.getDistributionTableFilters().getPinnedTarget().isPresent()) { + return; + } + } + + // service call to cancel the active action + private boolean cancelActiveAction(final Long actionId) { + if (actionId != null) { + try { + deploymentManagement.cancelAction(actionId); + return true; + } catch (final CancelActionNotAllowedException e) { + LOG.info("Cancel action not allowed exception :{}", e); + return false; + } + } + return false; + } + + // service call to cancel the active action + private boolean forceQuitActiveAction(final Long actionId) { + if (actionId != null) { + try { + deploymentManagement.forceQuitAction(actionId); + return true; + } catch (final CancelActionNotAllowedException e) { + LOG.info("Force Cancel action not allowed exception :{}", e); + return false; + } + } + return false; + } + + @Override + protected void setHiddenColumns() { + getColumn(VIRT_PROP_FORCED).setHidable(false); + getColumn(VIRT_PROP_TIMEFORCED).setHidable(false); + getColumn(VIRT_PROP_ACTION_CANCEL).setHidable(false); + getColumn(VIRT_PROP_ACTION_FORCE).setHidable(false); + getColumn(VIRT_PROP_ACTION_FORCE_QUIT).setHidable(false); + } + + @Override + protected CellDescriptionGenerator getDescriptionGenerator() { + return modTimetooltipGenerator; + } + + @Override + protected void setColumnHeaderNames() { + final HeaderRow newHeaderRow = resetHeaderDefaultRow(); + + getColumn(ProxyAction.PXY_ACTION_IS_ACTIVE_DECO).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE); + getColumn(ProxyAction.PXY_ACTION_DS_NAME_VERSION).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_DIST); + getColumn(ProxyAction.PXY_ACTION_LAST_MODIFIED_AT).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_DATETIME); + getColumn(ProxyAction.PXY_ACTION_STATUS).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_STATUS); + getColumn(VIRT_PROP_FORCED).setHeaderCaption(String.valueOf(forceClientRefreshToggle)); + forceClientRefreshToggle = !forceClientRefreshToggle; + + newHeaderRow.join(VIRT_PROP_FORCED, VIRT_PROP_TIMEFORCED).setText(SPUIDefinitions.ACTION_HIS_TBL_FORCED); + newHeaderRow.join(VIRT_PROP_ACTION_CANCEL, VIRT_PROP_ACTION_FORCE, VIRT_PROP_ACTION_FORCE_QUIT) + .setText(SPUIDefinitions.ACTIONS_COLUMN); + } + + @Override + protected void setColumnExpandRatio() { + setColumnsSize(50.0, 50.0, ProxyAction.PXY_ACTION_IS_ACTIVE_DECO); + setColumnsSize(107.0, 500.0, ProxyAction.PXY_ACTION_DS_NAME_VERSION); + setColumnsSize(100.0, 120.0, ProxyAction.PXY_ACTION_LAST_MODIFIED_AT); + setColumnsSize(53.0, 55.0, ProxyAction.PXY_ACTION_STATUS); + setColumnsSize(FIXED_PIX_MIN, FIXED_PIX_MIN, VIRT_PROP_FORCED, VIRT_PROP_TIMEFORCED, VIRT_PROP_ACTION_CANCEL, + VIRT_PROP_ACTION_FORCE, VIRT_PROP_ACTION_FORCE_QUIT); + } + + /** + * Conveniently sets min- and max-width for a bunch of columns. + * + * @param min + * minimum width + * @param max + * maximum width + * @param columnPropertyIds + * all the columns the min and max should be set for. + */ + private void setColumnsSize(final double min, final double max, final String... columnPropertyIds) { + for (final String columnPropertyId : columnPropertyIds) { + getColumn(columnPropertyId).setMinimumWidth(min); + getColumn(columnPropertyId).setMaximumWidth(max); + } + } + + /** + * Creates the grid content for maximized-state. + */ + private void createMaximizedContent() { + getSingleSelectionSupport().enable(); + getDetailsSupport().populateSelection(); + getMaximizeSupport().createMaximizedContent(); + recalculateColumnWidths(); + } + + /** + * Creates the grid content for normal (minimized) state. + */ + private void createMinimizedContent() { + getSingleSelectionSupport().disable(); + getMaximizeSupport().createMinimizedContent(); + recalculateColumnWidths(); + } + + @Override + protected void setColumnProperties() { + clearSortOrder(); + setColumns(minColumnOrder); + alignColumns(); + } + + /** + * Restores the maximized state if the action history was left in + * maximized-state and is now re-entered. + */ + private void restorePreviousState() { + if (managementUIState.isActionHistoryMaximized()) { + createMaximizedContent(); + } + } + + /** + * Sets the alignment cell-style-generator that handles the alignment for + * the grid cells. + */ + private void alignColumns() { + setCellStyleGenerator(alignGenerator); + } + + /** + * Adds support for virtual properties (aka generated properties) + */ + class ActionHistoryGeneratedPropertySupport extends AbstractGeneratedPropertySupport { + + @Override + public GeneratedPropertyContainer getDecoratedContainer() { + return (GeneratedPropertyContainer) getContainerDataSource(); + } + + @Override + public LazyQueryContainer getRawContainer() { + return (LazyQueryContainer) (getDecoratedContainer()).getWrappedContainer(); + } + + @Override + protected GeneratedPropertyContainer addGeneratedContainerProperties() { + final GeneratedPropertyContainer decoratedContainer = getDecoratedContainer(); + + decoratedContainer.addGeneratedProperty(VIRT_PROP_FORCED, new GenericPropertyValueGenerator()); + decoratedContainer.addGeneratedProperty(VIRT_PROP_TIMEFORCED, new GenericPropertyValueGenerator()); + decoratedContainer.addGeneratedProperty(VIRT_PROP_ACTION_CANCEL, new GenericPropertyValueGenerator()); + decoratedContainer.addGeneratedProperty(VIRT_PROP_ACTION_FORCE, new GenericPropertyValueGenerator()); + decoratedContainer.addGeneratedProperty(VIRT_PROP_ACTION_FORCE_QUIT, new GenericPropertyValueGenerator()); + + return decoratedContainer; + } + } + + /** + * Adds support to maximize the grid. + */ + class ActionHistoryMaximizeSupport extends AbstractMaximizeSupport { + + /** + * Sets the property-ids available in maximized-state. + */ + @Override + protected void setMaximizedColumnProperties() { + clearSortOrder(); + setColumns(maxColumnOrder); + alignColumns(); + } + + @Override + protected void setMaximizedHiddenColumns() { + getColumn(ProxyAction.PXY_ACTION_ID).setHidden(false); + getColumn(ProxyAction.PXY_ACTION_ID).setHidable(true); + getColumn(ProxyAction.PXY_ACTION_ROLLOUT_NAME).setHidden(false); + getColumn(ProxyAction.PXY_ACTION_ROLLOUT_NAME).setHidable(true); + } + + /** + * Sets additional headers for the maximized-state. + */ + @Override + protected void setMaximizedHeaders() { + getColumn(ProxyAction.PXY_ACTION_ID).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID); + getColumn(ProxyAction.PXY_ACTION_ROLLOUT_NAME) + .setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_ROLLOUT_NAME); + } + + /** + * Sets the expand ratio for the maximized-state. + */ + @Override + protected void setMaximizedColumnExpandRatio() { + /* set messages column can expand the rest of the available space */ + setColumnsSize(50.0, 50.0, ProxyAction.PXY_ACTION_IS_ACTIVE_DECO); + setColumnsSize(FIXED_PIX_MIN, 100.0, ProxyAction.PXY_ACTION_ID); + setColumnsSize(107.0, 500.0, ProxyAction.PXY_ACTION_DS_NAME_VERSION); + setColumnsSize(100.0, 150.0, ProxyAction.PXY_ACTION_LAST_MODIFIED_AT); + setColumnsSize(53.0, 55.0, ProxyAction.PXY_ACTION_STATUS); + setColumnsSize(FIXED_PIX_MIN, FIXED_PIX_MAX, VIRT_PROP_FORCED, VIRT_PROP_TIMEFORCED, + VIRT_PROP_ACTION_CANCEL, VIRT_PROP_ACTION_FORCE, VIRT_PROP_ACTION_FORCE_QUIT); + setColumnsSize(FIXED_PIX_MIN, 500.0, ProxyAction.PXY_ACTION_ROLLOUT_NAME); + } + } + + /** + * Concrete html-label converter that handles IsActiveDecoration enum. + */ + class HtmlIsActiveLabelConverter extends AbstractHtmlLabelConverter { + private static final long serialVersionUID = 1L; + + /** + * Constructor that sets the appropriate adapter. + * + * @param adapter + * adapts IsActiveDecoration to + * String + */ + public HtmlIsActiveLabelConverter(final LabelAdapter adapter) { + addAdapter(adapter); + } + + @Override + public Class getModelType() { + return IsActiveDecoration.class; + } + } + + /** + * Concrete html-label converter that handles Booleans. The converter needs + * an appropriate adapter that must be set via + * {@link AbstractHtmlLabelConverter#addAdapter()} to do the work. + */ + class HtmlBooleanLabelConverter extends AbstractHtmlLabelConverter { + private static final long serialVersionUID = 1L; + + @Override + public Class getModelType() { + return Boolean.class; + } + } + + /** + * Concrete html-label converter that handles Actions. + */ + class HtmlVirtPropLabelConverter extends AbstractHtmlLabelConverter { + private static final long serialVersionUID = 1L; + + /** + * Constructor that sets the appropriate adapter. + * + * @param adapter + * adapts Action to String + */ + public HtmlVirtPropLabelConverter(final LabelAdapter adapter) { + addAdapter(adapter); + } + + @Override + public Class getModelType() { + return Action.class; + } + } + + /** + * Concrete grid-button converter that handles Actions. + */ + class ActionGridButtonConverter extends AbstractGridButtonConverter { + private static final long serialVersionUID = 1L; + + /** + * Constructor that sets the appropriate adapter. + * + * @param adapter + * adapts Action to StatusFontIcon + */ + public ActionGridButtonConverter(final GridButtonAdapter adapter) { + addAdapter(adapter); + } + + @Override + public Class getModelType() { + return Action.class; + } + } + + /** + * Generator class responsible to retrieve an Action from the grid data in + * order to generate a virtual property. + */ + class GenericPropertyValueGenerator extends PropertyValueGenerator { + private static final long serialVersionUID = 1L; + + @Override + public Action getValue(final Item item, final Object itemId, final Object propertyId) { + return (Action) item.getItemProperty(ProxyAction.PXY_ACTION).getValue(); + } + + @Override + public Class getType() { + return Action.class; + } + } + + /** + * Generator class responsible to generate a button property value for the + * grid. + */ + class ButtonPropertyValueGenerator extends PropertyValueGenerator { + private static final long serialVersionUID = 1L; + + private final String buttonLabel; + + public ButtonPropertyValueGenerator(final String buttonLabel) { + this.buttonLabel = buttonLabel; + } + + @Override + public String getValue(final Item item, final Object itemId, final Object propertyId) { + return buttonLabel; + } + + @Override + public Class getType() { + return String.class; + } + } + + /** + * Configuration that holds the styling properties for label- and + * button-renderers that are included in grid-cells. + */ + public static class LabelConfig { + + /** + * Initializes a map with all available status label metadata. + * + * @param i18n + * @param statusLabelId + * @return the configured map + */ + public Map createStatusLabelConfig(final VaadinMessageSource i18n, + final String statusLabelId) { + final HashMap stateMap = Maps.newHashMapWithExpectedSize(9); + stateMap.put(Action.Status.FINISHED, new StatusFontIcon(FontAwesome.CHECK_CIRCLE, STATUS_ICON_GREEN, + i18n.getMessage("label.finished"), statusLabelId)); + stateMap.put(Action.Status.ERROR, new StatusFontIcon(FontAwesome.EXCLAMATION_CIRCLE, STATUS_ICON_RED, + i18n.getMessage("label.error"), statusLabelId)); + stateMap.put(Action.Status.WARNING, new StatusFontIcon(FontAwesome.EXCLAMATION_CIRCLE, STATUS_ICON_ORANGE, + i18n.getMessage("label.warning"), statusLabelId)); + stateMap.put(Action.Status.RUNNING, new StatusFontIcon(FontAwesome.ADJUST, STATUS_ICON_PENDING, + i18n.getMessage("label.running"), statusLabelId)); + stateMap.put(Action.Status.CANCELING, new StatusFontIcon(FontAwesome.TIMES_CIRCLE, STATUS_ICON_PENDING, + i18n.getMessage("label.cancelling"), statusLabelId)); + stateMap.put(Action.Status.CANCELED, new StatusFontIcon(FontAwesome.TIMES_CIRCLE, STATUS_ICON_GREEN, + i18n.getMessage("label.cancelled"), statusLabelId)); + stateMap.put(Action.Status.RETRIEVED, new StatusFontIcon(FontAwesome.CIRCLE_O, STATUS_ICON_PENDING, + i18n.getMessage("label.retrieved"), statusLabelId)); + stateMap.put(Action.Status.DOWNLOAD, new StatusFontIcon(FontAwesome.CLOUD_DOWNLOAD, STATUS_ICON_PENDING, + i18n.getMessage("label.download"), statusLabelId)); + stateMap.put(Action.Status.SCHEDULED, new StatusFontIcon(FontAwesome.HOURGLASS_1, STATUS_ICON_PENDING, + i18n.getMessage("label.scheduled"), statusLabelId)); + return stateMap; + } + + /** + * Initializes a map with all available active-state metadata. + * + * @param activeStateId + * @return the configured map + */ + public Map createActiveStatusLabelConfig(final String activeStateId) { + final HashMap activeStateMap = Maps.newHashMapWithExpectedSize(4); + activeStateMap.put(IsActiveDecoration.SCHEDULED, + new StatusFontIcon(FontAwesome.HOURGLASS_1, STATUS_ICON_PENDING, "Scheduled", activeStateId)); + activeStateMap.put(IsActiveDecoration.ACTIVE, + new StatusFontIcon(null, STATUS_ICON_ACTIVE, "Active", activeStateId)); + activeStateMap.put(IsActiveDecoration.IN_ACTIVE, + new StatusFontIcon(FontAwesome.CHECK_CIRCLE, STATUS_ICON_NEUTRAL, "In-active", activeStateId)); + activeStateMap.put(IsActiveDecoration.IN_ACTIVE_ERROR, + new StatusFontIcon(FontAwesome.CHECK_CIRCLE, STATUS_ICON_RED, "In-active", activeStateId)); + return activeStateMap; + } + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryHeader.java deleted file mode 100644 index d61d53a95..000000000 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryHeader.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; - -import org.eclipse.hawkbit.ui.common.builder.LabelBuilder; -import org.eclipse.hawkbit.ui.components.SPUIButton; -import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; -import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; -import org.eclipse.hawkbit.ui.management.event.ManagementUIEvent; -import org.eclipse.hawkbit.ui.management.state.ManagementUIState; -import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; -import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; -import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; -import org.vaadin.spring.events.EventBus; -import org.vaadin.spring.events.EventBus.UIEventBus; - -import com.vaadin.server.FontAwesome; -import com.vaadin.shared.ui.label.ContentMode; -import com.vaadin.ui.Alignment; -import com.vaadin.ui.HorizontalLayout; -import com.vaadin.ui.Label; -import com.vaadin.ui.VerticalLayout; - -/** - * - * - */ -public class ActionHistoryHeader extends VerticalLayout { - private static final long serialVersionUID = -6276188234115774351L; - - private final transient EventBus.UIEventBus eventBus; - private final ManagementUIState managementUIState; - - private Label titleOfActionHistory; - private SPUIButton maxMinButton; - - ActionHistoryHeader(final UIEventBus eventBus, final ManagementUIState managementUIState) { - this.eventBus = eventBus; - this.managementUIState = managementUIState; - buildComponent(); - buildLayout(); - restorePreviousState(); - } - - private void buildComponent() { - // create default title - it will be shown even when no data is - // available - titleOfActionHistory = new LabelBuilder().name(HawkbitCommonUtil.getArtifactoryDetailsLabelId("")) - .buildCaptionLabel(); - - titleOfActionHistory.setImmediate(true); - titleOfActionHistory.setContentMode(ContentMode.HTML); - - maxMinButton = (SPUIButton) SPUIComponentProvider.getButton(SPUIDefinitions.EXPAND_ACTION_HISTORY, "", "", null, - true, FontAwesome.EXPAND, SPUIButtonStyleSmallNoBorder.class); - // listener for maximizing action history - maxMinButton.addClickListener(event -> maxMinButtonClicked()); - - } - - private void buildLayout() { - final HorizontalLayout titleMaxIconsLayout = new HorizontalLayout(); - titleMaxIconsLayout.addStyleName(SPUIStyleDefinitions.WIDGET_TITLE); - titleMaxIconsLayout.setSpacing(false); - titleMaxIconsLayout.setMargin(false); - titleMaxIconsLayout.setSizeFull(); - titleMaxIconsLayout.addComponents(titleOfActionHistory, maxMinButton); - titleMaxIconsLayout.setComponentAlignment(titleOfActionHistory, Alignment.TOP_LEFT); - titleMaxIconsLayout.setComponentAlignment(maxMinButton, Alignment.TOP_RIGHT); - titleMaxIconsLayout.setExpandRatio(titleOfActionHistory, 0.8f); - titleMaxIconsLayout.setExpandRatio(maxMinButton, 0.2f); - - // Note: here the only purpose of adding drop hints to the layout is to - // maintain consistent - // height for all widgets headers. - addComponent(titleMaxIconsLayout); - setComponentAlignment(titleMaxIconsLayout, Alignment.TOP_LEFT); - setWidth(100, Unit.PERCENTAGE); - setImmediate(true); - addStyleName("action-history-header"); - addStyleName("bordered-layout"); - addStyleName("no-border-bottom"); - } - - /** - * Populate Header Data for Target. - * - * @param targetName - * name of the target - */ - public void populateHeader(final String targetName) { - updateActionHistoryHeader(targetName); - } - - private void maxMinButtonClicked() { - final Boolean flag = (Boolean) maxMinButton.getData(); - if (flag == null || Boolean.FALSE.equals(flag)) { - // Clicked on max Icon - maximizedTableView(); - managementUIState.setActionHistoryMaximized(Boolean.TRUE); - } else { - // Clicked on min icon - minimizeTableView(); - managementUIState.setActionHistoryMaximized(Boolean.FALSE); - } - } - - private void maximizedTableView() { - showMinIcon(); - eventBus.publish(this, ManagementUIEvent.MAX_ACTION_HISTORY); - } - - private void minimizeTableView() { - showMaxIcon(); - eventBus.publish(this, ManagementUIEvent.MIN_ACTION_HISTORY); - } - - /** - * Updates header with target name. - * - * @param targetName - * name of the target - */ - public void updateActionHistoryHeader(final String targetName) { - titleOfActionHistory.setValue(HawkbitCommonUtil.getActionHistoryLabelId(targetName)); - } - - private void showMinIcon() { - maxMinButton.togleIcon(FontAwesome.COMPRESS); - maxMinButton.setData(Boolean.TRUE); - } - - private void showMaxIcon() { - maxMinButton.togleIcon(FontAwesome.EXPAND); - maxMinButton.setData(Boolean.FALSE); - } - - private void restorePreviousState() { - if (managementUIState.isActionHistoryMaximized()) { - showMinIcon(); - } - } -} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryLayout.java new file mode 100644 index 000000000..e1411c68e --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryLayout.java @@ -0,0 +1,212 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.ui.common.grid.AbstractGrid; +import org.eclipse.hawkbit.ui.common.grid.AbstractGridComponentLayout; +import org.eclipse.hawkbit.ui.common.grid.DefaultGridHeader; +import org.eclipse.hawkbit.ui.common.grid.DefaultGridHeader.AbstractHeaderMaximizeSupport; +import org.eclipse.hawkbit.ui.common.table.BaseEntityEventType; +import org.eclipse.hawkbit.ui.management.event.ManagementUIEvent; +import org.eclipse.hawkbit.ui.management.event.TargetTableEvent; +import org.eclipse.hawkbit.ui.management.state.ManagementUIState; +import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.UINotification; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.vaadin.spring.events.EventBus.UIEventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.vaadin.ui.UI; + +/** + * Layout responsible for action-history-grid and the corresponding header. + */ +public class ActionHistoryLayout extends AbstractGridComponentLayout { + private static final long serialVersionUID = -3766179797384539821L; + + private final transient DeploymentManagement deploymentManagement; + private final UINotification notification; + private final ManagementUIState managementUIState; + + private transient AbstractGrid.DetailsSupport details; + private Long masterForDetails; + + /** + * Constructor. + * + * @param i18n + * @param deploymentManagement + * @param eventBus + * @param notification + * @param managementUIState + */ + public ActionHistoryLayout(final VaadinMessageSource i18n, final DeploymentManagement deploymentManagement, + final UIEventBus eventBus, final UINotification notification, final ManagementUIState managementUIState) { + super(i18n, eventBus); + this.deploymentManagement = deploymentManagement; + this.notification = notification; + this.managementUIState = managementUIState; + init(); + } + + @Override + public ActionHistoryHeader createGridHeader() { + return new ActionHistoryHeader(managementUIState).init(); + } + + @Override + public ActionHistoryGrid createGrid() { + return new ActionHistoryGrid(i18n, deploymentManagement, eventBus, notification, managementUIState); + } + + @EventBusListenerMethod(scope = EventScope.UI) + void onEvent(final TargetTableEvent targetUIEvent) { + if (BaseEntityEventType.SELECTED_ENTITY == targetUIEvent.getEventType()) { + setData(SPUIDefinitions.DATA_AVAILABLE); + UI.getCurrent().access(() -> populateActionHistoryDetails(targetUIEvent.getEntity())); + } else if (BaseEntityEventType.REMOVE_ENTITY == targetUIEvent.getEventType() + && targetUIEvent.getEntityIds().contains(managementUIState.getLastSelectedTargetId())) { + setData(SPUIDefinitions.NO_DATA); + UI.getCurrent().access(this::populateActionHistoryDetails); + } + } + + /** + * Override default registration for selection propagation in order to + * interrupt update cascade in minimized state to prevent updates on + * invisible action-status-grid and message-grid. + *

+ * The master selection is stored and propagation is performed as soon as + * the state changes to maximize and hence the dependent grids are updated. + */ + @Override + public void registerDetails(final AbstractGrid.DetailsSupport details) { + this.details = details; + getGrid().addSelectionListener(event -> { + masterForDetails = (Long) event.getSelected().stream().findFirst().orElse(null); + if (managementUIState.isActionHistoryMaximized()) { + details.populateMasterDataAndRecalculateContainer(masterForDetails); + } + }); + } + + /** + * Populate action header and table for the target. + * + * @param target + * the target + */ + public void populateActionHistoryDetails(final Target target) { + if (null != target) { + ((ActionHistoryHeader) getHeader()).updateActionHistoryHeader(target.getName()); + ((ActionHistoryGrid) getGrid()).populateSelectedTarget(target); + } else { + ((ActionHistoryHeader) getHeader()).updateActionHistoryHeader(" "); + } + } + + /** + * Populate empty action header and empty table for empty selection. + */ + public void populateActionHistoryDetails() { + ((ActionHistoryHeader) getHeader()).updateActionHistoryHeader(" "); + ((ActionHistoryGrid) getGrid()).populateSelectedTarget(null); + } + + /** + * Header for ActionHistory with maximize-support. + */ + class ActionHistoryHeader extends DefaultGridHeader { + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + * @param managementUIState + */ + ActionHistoryHeader(final ManagementUIState managementUIState) { + super(managementUIState); + this.setHeaderMaximizeSupport( + new ActionHistoryHeaderMaxSupport(this, SPUIDefinitions.EXPAND_ACTION_HISTORY)); + } + + /** + * Initializes the header. + */ + @Override + public ActionHistoryHeader init() { + super.init(); + restorePreviousState(); + return this; + } + + /** + * Updates header with target name. + * + * @param targetName + * name of the target + */ + public void updateActionHistoryHeader(final String targetName) { + updateTitle(HawkbitCommonUtil.getActionHistoryLabelId(targetName)); + } + + /** + * Restores the previous min-max state. + */ + private void restorePreviousState() { + if (hasHeaderMaximizeSupport() && managementUIState.isActionHistoryMaximized()) { + getHeaderMaximizeSupport().showMinIcon(); + } + } + } + + /** + * Min-max support for header. + */ + class ActionHistoryHeaderMaxSupport extends AbstractHeaderMaximizeSupport { + + private final DefaultGridHeader abstractGridHeader; + + /** + * Constructor. + * + * @param abstractGridHeader + * @param maximizeButtonId + */ + protected ActionHistoryHeaderMaxSupport(final DefaultGridHeader abstractGridHeader, + final String maximizeButtonId) { + abstractGridHeader.super(maximizeButtonId); + this.abstractGridHeader = abstractGridHeader; + } + + @Override + protected void maximize() { + details.populateMasterDataAndRecreateContainer(masterForDetails); + eventBus.publish(this, ManagementUIEvent.MAX_ACTION_HISTORY); + } + + @Override + protected void minimize() { + eventBus.publish(this, ManagementUIEvent.MIN_ACTION_HISTORY); + } + + /** + * Gets the grid header the maximize support is for. + * + * @return grid header + */ + protected DefaultGridHeader getGridHeader() { + return abstractGridHeader; + } + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryTable.java deleted file mode 100644 index 6a1ebf482..000000000 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryTable.java +++ /dev/null @@ -1,844 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.StringJoiner; - -import org.eclipse.hawkbit.repository.ActionStatusFields; -import org.eclipse.hawkbit.repository.DeploymentManagement; -import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; -import org.eclipse.hawkbit.repository.model.Action; -import org.eclipse.hawkbit.repository.model.Action.ActionType; -import org.eclipse.hawkbit.repository.model.ActionStatus; -import org.eclipse.hawkbit.repository.model.ActionWithStatusCount; -import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.ui.common.ConfirmationDialog; -import org.eclipse.hawkbit.ui.common.builder.LabelBuilder; -import org.eclipse.hawkbit.ui.common.entity.TargetIdName; -import org.eclipse.hawkbit.ui.common.table.BaseEntityEventType; -import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; -import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; -import org.eclipse.hawkbit.ui.management.event.ManagementUIEvent; -import org.eclipse.hawkbit.ui.management.event.PinUnpinEvent; -import org.eclipse.hawkbit.ui.management.event.TargetTableEvent; -import org.eclipse.hawkbit.ui.management.state.ManagementUIState; -import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; -import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; -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.UINotification; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.domain.Sort.Direction; -import org.vaadin.spring.events.EventBus; -import org.vaadin.spring.events.EventBus.UIEventBus; -import org.vaadin.spring.events.EventScope; -import org.vaadin.spring.events.annotation.EventBusListenerMethod; - -import com.vaadin.data.Container; -import com.vaadin.data.Item; -import com.vaadin.data.util.HierarchicalContainer; -import com.vaadin.server.FontAwesome; -import com.vaadin.shared.ui.label.ContentMode; -import com.vaadin.ui.Button; -import com.vaadin.ui.Component; -import com.vaadin.ui.HorizontalLayout; -import com.vaadin.ui.Label; -import com.vaadin.ui.Table; -import com.vaadin.ui.TextArea; -import com.vaadin.ui.TreeTable; -import com.vaadin.ui.UI; -import com.vaadin.ui.themes.ValoTheme; - -/** - * Table for {@link Target#getActions()} history. - * - */ -public class ActionHistoryTable extends TreeTable { - - private static final long serialVersionUID = -1631514704696786653L; - private static final Logger LOG = LoggerFactory.getLogger(ActionHistoryTable.class); - private static final String BUTTON_CANCEL = "button.cancel"; - private static final String BUTTON_OK = "button.ok"; - - private final VaadinMessageSource i18n; - private final transient DeploymentManagement deploymentManagement; - private final transient EventBus.UIEventBus eventBus; - private final UINotification notification; - private final ManagementUIState managementUIState; - - private Container hierarchicalContainer; - private boolean alreadyHasMessages; - - private Target target; - - ActionHistoryTable(final VaadinMessageSource i18n, final DeploymentManagement deploymentManagement, final UIEventBus eventBus, - final UINotification notification, final ManagementUIState managementUIState) { - this.i18n = i18n; - this.deploymentManagement = deploymentManagement; - this.eventBus = eventBus; - this.notification = notification; - this.managementUIState = managementUIState; - - initializeTableSettings(); - buildComponent(); - restorePreviousState(); - setVisibleColumns(getVisbleColumns().toArray()); - eventBus.subscribe(this); - setPageLength(SPUIDefinitions.PAGE_SIZE); - } - - @EventBusListenerMethod(scope = EventScope.UI) - void onEvent(final ManagementUIEvent mgmtUIEvent) { - if (mgmtUIEvent == ManagementUIEvent.MAX_ACTION_HISTORY) { - UI.getCurrent().access(this::createTableContentForMax); - } - if (mgmtUIEvent == ManagementUIEvent.MIN_ACTION_HISTORY) { - UI.getCurrent().access(this::normalActionHistoryTable); - } - } - - /* - * this method is called when the target history component is initialized - * without any data - */ - private void buildComponent() { - // create an empty container - createContainer(); - setContainerDataSource(hierarchicalContainer); - addGeneratedColumns(); - setColumnExpandRatioForMinimisedTable(); - } - - private void setColumnExpandRatioForMinimisedTable() { - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID, 0.1F); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DIST, 0.3F); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_STATUS, 0.15F); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DATETIME, 0.3F); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_FORCED, 0.15F); - setColumnExpandRatio(SPUIDefinitions.ACTIONS_COLUMN, 0.2F); - } - - private void initializeTableSettings() { - - setId(UIComponentIdProvider.ACTION_HISTORY_TABLE_ID); - setSelectable(false); - setMultiSelect(false); - setSortEnabled(true); - setColumnReorderingAllowed(true); - setHeight(100.0F, Unit.PERCENTAGE); - setWidth(100.0F, Unit.PERCENTAGE); - setImmediate(true); - setStyleName("sp-table"); - addStyleName(ValoTheme.TABLE_NO_VERTICAL_LINES); - addStyleName(ValoTheme.TABLE_SMALL); - setColumnAlignment(SPUIDefinitions.ACTION_HIS_TBL_FORCED, Align.CENTER); - setColumnAlignment(SPUIDefinitions.ACTION_HIS_TBL_STATUS, Align.CENTER); - setColumnAlignment(SPUIDefinitions.ACTIONS_COLUMN, Align.CENTER); - // listeners for child - addExpandListener(event -> { - expandParentActionRow(event.getItemId()); - managementUIState.getExpandParentActionRowId().add(event.getItemId()); - }); - addCollapseListener(event -> { - collapseParentActionRow(event.getItemId()); - managementUIState.getExpandParentActionRowId().remove(event.getItemId()); - }); - } - - /** - * Create a empty HierarchicalContainer. - */ - public void createContainer() { - /* Create HierarchicalContainer container */ - hierarchicalContainer = new HierarchicalContainer(); - hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE_HIDDEN, String.class, null); - hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_FORCED, Action.class, null); - hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID_HIDDEN, Long.class, null); - hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID, String.class, null); - hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_DIST, String.class, null); - hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_DATETIME, String.class, null); - hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN, Action.Status.class, - null); - hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_MSGS_HIDDEN, List.class, null); - hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_ROLLOUT_NAME, String.class, null); - - } - - private List getVisbleColumns() { - final List visibleColumnIds = new ArrayList<>(); - visibleColumnIds.add(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE); - visibleColumnIds.add(SPUIDefinitions.ACTION_HIS_TBL_DIST); - visibleColumnIds.add(SPUIDefinitions.ACTION_HIS_TBL_DATETIME); - visibleColumnIds.add(SPUIDefinitions.ACTION_HIS_TBL_STATUS); - visibleColumnIds.add(SPUIDefinitions.ACTION_HIS_TBL_FORCED); - visibleColumnIds.add(SPUIDefinitions.ACTIONS_COLUMN); - - if (managementUIState.isActionHistoryMaximized()) { - visibleColumnIds.add(SPUIDefinitions.ACTION_HIS_TBL_ROLLOUT_NAME); - visibleColumnIds.add(SPUIDefinitions.ACTION_HIS_TBL_MSGS); - visibleColumnIds.add(1, SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID); - } - return visibleColumnIds; - } - - /** - * fetch the target details using controller id, and set it globally. - * - * @param selectedTarget - * reference of target - */ - public void populateTableData(final Target selectedTarget) { - target = selectedTarget; - refreshContainer(); - } - - /* re -create the container and get the data and set it to the table */ - private void refreshContainer() { - getcontainerData(); - // to expand parent row , if already expanded. - expandParentRow(); - - } - - private void getcontainerData() { - hierarchicalContainer.removeAllItems(); - - if (target != null) { - /* service method to create action history for target */ - final List actionHistory = deploymentManagement - .findActionsWithStatusCountByTargetOrderByIdDesc(target.getControllerId()); - - addDetailsToContainer(actionHistory); - } - } - - /** - * Populate Container for Action. - * - * @param isActiveActions - * as flag - * @param reversedActions - * as action - * @param startIdx - * as sort - * @param target2 - * @param actionHistoryMode - * as either {@link ActionHistoryMode.NORMAL} or - * {@link ActionHistoryMode.MAXIMIZED} - */ - @SuppressWarnings("unchecked") - private void addDetailsToContainer(final List actions) { - for (final ActionWithStatusCount actionWithStatusCount : actions) { - - final Action action = actionWithStatusCount.getAction(); - - final Item item = hierarchicalContainer.addItem(actionWithStatusCount.getAction().getId()); - - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN) - .setValue(actionWithStatusCount.getAction().getStatus()); - - /* - * add action id. - */ - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID) - .setValue(actionWithStatusCount.getAction().getId().toString()); - /* - * add active/inactive status to the item which will be used in - * Column generator to generate respective icon - */ - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE_HIDDEN).setValue( - actionWithStatusCount.getAction().isActive() ? SPUIDefinitions.ACTIVE : SPUIDefinitions.IN_ACTIVE); - - /* - * add action Id to the item which will be used for fetching child - * items ( previous action status ) during expand - */ - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID_HIDDEN) - .setValue(actionWithStatusCount.getAction().getId()); - - /* - * add distribution name to the item which will be displayed in the - * table. The name should not exceed certain limit. - */ - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DIST) - .setValue(actionWithStatusCount.getDsName() + ":" + actionWithStatusCount.getDsVersion()); - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_FORCED).setValue(action); - - /* Default no child */ - ((Hierarchical) hierarchicalContainer).setChildrenAllowed(actionWithStatusCount.getAction().getId(), false); - - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DATETIME) - .setValue(SPDateTimeUtil.getFormattedDate(actionWithStatusCount.getAction().getLastModifiedAt())); - - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ROLLOUT_NAME) - .setValue(actionWithStatusCount.getRolloutName()); - - if (actionWithStatusCount.getActionStatusCount() > 0) { - ((Hierarchical) hierarchicalContainer).setChildrenAllowed(actionWithStatusCount.getAction().getId(), - true); - } - } - } - - private void addGeneratedColumns() { - addGeneratedColumn(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE, new Table.ColumnGenerator() { - /* Serial Verion Id */ - private static final long serialVersionUID = -8673604389011758339L; - - @Override - public Component generateCell(final Table source, final Object itemId, final Object columnId) { - return getActiveColumn(itemId); - } - }); - addGeneratedColumn(SPUIDefinitions.ACTION_HIS_TBL_STATUS, new Table.ColumnGenerator() { - /* Serial Verion Id */ - private static final long serialVersionUID = 1L; - - @Override - public Component generateCell(final Table source, final Object itemId, final Object columnId) { - return getStatusColumn(itemId); - } - }); - // forced - addGeneratedColumn(SPUIDefinitions.ACTION_HIS_TBL_FORCED, new Table.ColumnGenerator() { - /* Serial Verion Id */ - private static final long serialVersionUID = 1L; - - @Override - public Component generateCell(final Table source, final Object itemId, final Object columnId) { - return getForcedColumn(itemId); - } - }); - - addGeneratedColumn(SPUIDefinitions.ACTIONS_COLUMN, new Table.ColumnGenerator() { - - private static final long serialVersionUID = 1L; - - @Override - public Component generateCell(final Table source, final Object itemId, final Object columnId) { - return createActionBarColumn(itemId); - } - }); - } - - private Component getForcedColumn(final Object itemId) { - final Action actionWithActiveStatus = (Action) hierarchicalContainer.getItem(itemId) - .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_FORCED).getValue(); - final Label actionLabel = new LabelBuilder().name("").buildCaptionLabel(); - actionLabel.setContentMode(ContentMode.HTML); - actionLabel.setStyleName("action-history-table-col-forced-label"); - if (actionWithActiveStatus != null && actionWithActiveStatus.getActionType() == ActionType.FORCED) { - actionLabel.setValue(FontAwesome.BOLT.getHtml()); - // setted Id for Forced. - actionLabel.setId("action.history.table.forcedId"); - } else if (actionWithActiveStatus != null && actionWithActiveStatus.getActionType() == ActionType.TIMEFORCED) { - return actionLabelWithTimeForceIcon(actionWithActiveStatus, actionLabel); - } - return actionLabel; - } - - private Component getActiveColumn(final Object itemId) { - final Action.Status status = (Action.Status) hierarchicalContainer.getItem(itemId) - .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN).getValue(); - String activeValue; - if (status == Action.Status.SCHEDULED) { - activeValue = Action.Status.SCHEDULED.toString().toLowerCase(); - } else { - activeValue = (String) hierarchicalContainer.getItem(itemId) - .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE_HIDDEN).getValue(); - } - final String distName = (String) hierarchicalContainer.getItem(itemId) - .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DIST).getValue(); - final Label activeStatusIcon = createActiveStatusLabel(activeValue, - (Action.Status) hierarchicalContainer.getItem(itemId) - .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN) - .getValue() == Action.Status.ERROR); - activeStatusIcon.setId(new StringJoiner(".").add(distName).add(itemId.toString()) - .add(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE).add(activeValue).toString()); - return activeStatusIcon; - } - - private HorizontalLayout createActionBarColumn(final Object itemId) { - final HorizontalLayout actionBar = new HorizontalLayout(); - final Item item = hierarchicalContainer.getItem(itemId); - final Long actionId = (Long) item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID_HIDDEN).getValue(); - final String activeValue = (String) item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE_HIDDEN) - .getValue(); - final Action actionWithActiveStatus = (Action) item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_FORCED) - .getValue(); - - if (actionWithActiveStatus == null) { - return null; - } - final boolean isActionActive = target != null && SPUIDefinitions.ACTIVE.equals(activeValue); - - final Button actionCancel = SPUIComponentProvider.getButton( - UIComponentIdProvider.ACTION_HISTORY_TABLE_CANCEL_ID, "", i18n.getMessage("message.cancel.action"), - ValoTheme.BUTTON_TINY, true, FontAwesome.TIMES, SPUIButtonStyleSmallNoBorder.class); - actionCancel.setEnabled(isActionActive && !actionWithActiveStatus.isCancelingOrCanceled()); - actionCancel.addClickListener(event -> confirmAndCancelAction(actionId)); - - final Button actionForce = SPUIComponentProvider.getButton(UIComponentIdProvider.ACTION_HISTORY_TABLE_FORCE_ID, - "", i18n.getMessage("message.force.action"), ValoTheme.BUTTON_TINY, true, FontAwesome.BOLT, - SPUIButtonStyleSmallNoBorder.class); - actionForce.setEnabled( - isActionActive && !actionWithActiveStatus.isForce() && !actionWithActiveStatus.isCancelingOrCanceled()); - actionForce.addClickListener(event -> confirmAndForceAction(actionId)); - - final Button actionForceQuit = SPUIComponentProvider.getButton( - UIComponentIdProvider.ACTION_HISTORY_TABLE_FORCE_QUIT_ID, "", i18n.getMessage("message.forcequit.action"), - ValoTheme.BUTTON_TINY + " redicon", true, FontAwesome.TIMES, SPUIButtonStyleSmallNoBorder.class); - actionForceQuit.setEnabled(isActionActive && actionWithActiveStatus.isCancelingOrCanceled()); - actionForceQuit.addClickListener(event -> confirmAndForceQuitAction(actionId)); - - actionBar.addComponents(actionCancel, actionForce, actionForceQuit); - - return actionBar; - } - - private Component getStatusColumn(final Object itemId) { - final Action.Status status = (Action.Status) hierarchicalContainer.getItem(itemId) - .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN).getValue(); - return getStatusIcon(status); - } - - /** - * Load the rows of previous status history of the selected action row and - * add it next to the selected action row. - * - * @param parentRowIdx - * index of the selected action row. - */ - @SuppressWarnings("unchecked") - private void expandParentActionRow(final Object parentRowIdx) { - /* Get the item for which the expand is made */ - final Item item = hierarchicalContainer.getItem(parentRowIdx); - if (null != item) { - final Long actionId = (Long) item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID_HIDDEN) - .getValue(); - - final Optional action = deploymentManagement.findActionWithDetails(actionId); - - if (!action.isPresent()) { - return; - } - - final Pageable pageReq = new PageRequest(0, 1000, - new Sort(Direction.DESC, ActionStatusFields.ID.getFieldName())); - final Page actionStatusList; - if (managementUIState.isActionHistoryMaximized()) { - actionStatusList = deploymentManagement.findActionStatusByActionWithMessages(pageReq, actionId); - } else { - actionStatusList = deploymentManagement.findActionStatusByAction(pageReq, actionId); - } - final List content = actionStatusList.getContent(); - /* - * Since the recent action status and messages are already - * displaying with parent item, check if more than one action status - * available. - */ - int childIdx = 1; - for (final ActionStatus actionStatus : content) { - final String childId = parentRowIdx + " -> " + childIdx; - final Item childItem = hierarchicalContainer.addItem(childId); - if (null != childItem) { - /* - * For better UI, no need to display active/inactive icon - * for child items. - */ - childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE_HIDDEN).setValue(""); - - childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DIST) - .setValue(action.get().getDistributionSet().getName() + ":" - + action.get().getDistributionSet().getVersion()); - - childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DATETIME) - .setValue(SPDateTimeUtil.getFormattedDate(actionStatus.getCreatedAt())); - childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN) - .setValue(actionStatus.getStatus()); - showOrHideMessage(childItem, actionStatus); - /* No further child items allowed for the child items */ - ((Hierarchical) hierarchicalContainer).setChildrenAllowed(childId, false); - /* Assign this childItem to the parent */ - ((Hierarchical) hierarchicalContainer).setParent(childId, parentRowIdx); - childIdx++; - - } - } - } - } - - /** - * Hide the rows of previous status history of the selected action row. - * - * @param parentRowIdx - * index of the selected action row. - */ - public void collapseParentActionRow(final Object parentRowIdx) { - /* Remove all child items for the clear the memory. */ - final Collection children = ((Hierarchical) hierarchicalContainer).getChildren(parentRowIdx); - if (children != null && !children.isEmpty()) { - String ids = children.toString().substring(1); - ids = ids.substring(0, ids.length() - 1); - for (final String childId : ids.split(", ")) { - ((Hierarchical) hierarchicalContainer).removeItem(childId); - } - } - } - - /** - * Get status icon. - * - * @param status - * as Status - * @return Label as UI - */ - private Label getStatusIcon(final Action.Status status) { - final Label label = new LabelBuilder().name("").buildLabel(); - label.setContentMode(ContentMode.HTML); - - final ActionStatusIconMapper mapping = ActionStatusIconMapper.MAPPINGS.get(status); - - if (mapping == null) { - label.setDescription(""); - label.setValue(""); - return label; - } - - label.setDescription(i18n.getMessage(mapping.getDescriptionI18N())); - label.setStyleName(mapping.getStyleName()); - label.setValue(mapping.getIcon().getHtml()); - return label; - - } - - // to show forced Icon - private Component actionLabelWithTimeForceIcon(final Action actionWithActiveStatus, final Label actionLabel) { - final long currentTimeMillis = System.currentTimeMillis(); - - final HorizontalLayout hLayout = new HorizontalLayout(); - final Label autoForceLabel = new LabelBuilder().name("").id("action.history.table.timedforceId").buildLabel(); - - actionLabel.setValue(FontAwesome.BOLT.getHtml()); - autoForceLabel.setContentMode(ContentMode.HTML); - autoForceLabel.setValue(FontAwesome.HISTORY.getHtml()); - - hLayout.addComponent(actionLabel); - hLayout.addComponent(autoForceLabel); - - if (actionWithActiveStatus.isHitAutoForceTime(currentTimeMillis)) { - autoForceLabel.setDescription("autoforced"); - autoForceLabel.setStyleName(SPUIStyleDefinitions.STATUS_ICON_GREEN); - autoForceLabel.setDescription("auto forced since " + SPDateTimeUtil - .getDurationFormattedString(actionWithActiveStatus.getForcedTime(), currentTimeMillis, i18n)); - } else { - autoForceLabel.setDescription("auto forcing in " + SPDateTimeUtil - .getDurationFormattedString(currentTimeMillis, actionWithActiveStatus.getForcedTime(), i18n)); - autoForceLabel.setStyleName("statusIconPending"); - autoForceLabel.setValue(FontAwesome.HISTORY.getHtml()); - } - return hLayout; - } - - /** - * Create Status Label. - * - * @param activeValue - * as String - * @return Labeal as UI - */ - private static Label createActiveStatusLabel(final String activeValue, final boolean endedWithError) { - final Label label = new LabelBuilder().name("").buildLabel(); - label.setContentMode(ContentMode.HTML); - if (SPUIDefinitions.SCHEDULED.equals(activeValue)) { - label.setDescription("Scheduled"); - label.setValue(FontAwesome.HOURGLASS_1.getHtml()); - } else if (SPUIDefinitions.ACTIVE.equals(activeValue)) { - label.setDescription("Active"); - label.setStyleName("statusIconActive"); - } else if (SPUIDefinitions.IN_ACTIVE.equals(activeValue)) { - if (endedWithError) { - label.setStyleName(SPUIStyleDefinitions.STATUS_ICON_RED); - } else { - label.setStyleName("statusIconNeutral"); - } - label.setDescription("In-active"); - label.setValue(FontAwesome.CHECK_CIRCLE.getHtml()); - } else { - label.setValue(""); - } - return label; - } - - /** - * Creates full sized window for action history with message. - */ - private void createTableContentForMax() { - setColumnCollapsingAllowed(true); - if (!alreadyHasMessages) { - /* - * check to avoid DB call for fetching the messages again and again - * if already available in container. - */ - getcontainerData(); - // to expand parent row , if already expanded. - expandParentRow(); - alreadyHasMessages = true; - } - - addGeneratedColumn(SPUIDefinitions.ACTION_HIS_TBL_MSGS, new Table.ColumnGenerator() { - private static final long serialVersionUID = 1L; - - @SuppressWarnings("unchecked") - @Override - public Component generateCell(final Table source, final Object itemId, final Object columnId) { - final List messages = (List) hierarchicalContainer.getItem(itemId) - .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_MSGS_HIDDEN).getValue(); - return createMessagesBlock(messages); - } - }); - setVisibleColumns(getVisbleColumns().toArray()); - setColumnExpantRatioOnTableMaximize(); - } - - private void setColumnExpantRatioOnTableMaximize() { - /* set messages column can expand the rest of the available space */ - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE, 0.1F); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID, 0.1F); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_STATUS, 0.1F); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DIST, 0.2F); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_FORCED, 0.1F); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_ROLLOUT_NAME, 0.1F); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_MSGS, 0.35F); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DATETIME, 0.15F); - setColumnExpandRatio(SPUIDefinitions.ACTIONS_COLUMN, 0.2F); - } - - /** - * create Message block for Actions. - * - * @param messages - * as List of msg - * @return Component as UI - */ - protected Component createMessagesBlock(final List messages) { - - final TextArea textArea = new TextArea(); - textArea.addStyleName(ValoTheme.TEXTAREA_BORDERLESS); - textArea.addStyleName(ValoTheme.TEXTAREA_TINY); - textArea.addStyleName("inline-icon"); - textArea.setSizeFull(); - int index = 1; - final StringBuilder updateStatusMessages = new StringBuilder(); - if (messages != null && !messages.isEmpty()) { - /* Messages are available */ - for (final String msg : messages) { - updateStatusMessages.append('[').append(index).append("]: ").append(msg).append('\n'); - index++; - } - } else { - /* Messages are not available */ - updateStatusMessages.append(i18n.getMessage("message.no.available")); - } - textArea.setValue(updateStatusMessages.toString()); - textArea.setReadOnly(Boolean.TRUE); - return textArea; - } - - /** - * Show or hide message. - * - * @param actionHistoryMode - * {@link ActionHisTableType} - * @param childItem - * {@link Item} - * @param actionStatus - * {@link ActionHisTableType} - */ - @SuppressWarnings("unchecked") - private void showOrHideMessage(final Item childItem, final ActionStatus actionStatus) { - if (managementUIState.isActionHistoryMaximized()) { - childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_MSGS_HIDDEN).setValue(actionStatus.getMessages()); - } - } - - /** - * View History Table in normal window mode. - */ - private void normalActionHistoryTable() { - setColumnCollapsingAllowed(false); - managementUIState.setActionHistoryMaximized(false); - removeGeneratedColumn(SPUIDefinitions.ACTION_HIS_TBL_MSGS); - setVisibleColumns(getVisbleColumns().toArray()); - setColumnExpandRatioForMinimisedTable(); - } - - /** - * Show confirmation window and if ok then only, force the action. - * - * @param actionId - * as Id if the action needs to be forced. - */ - private void confirmAndForceAction(final Long actionId) { - /* Display the confirmation */ - final ConfirmationDialog confirmDialog = new ConfirmationDialog(i18n.getMessage("caption.force.action.confirmbox"), - i18n.getMessage("message.force.action.confirm"), i18n.getMessage(BUTTON_OK), i18n.getMessage(BUTTON_CANCEL), ok -> { - if (!ok) { - return; - } - deploymentManagement.forceTargetAction(actionId); - populateAndupdateTargetDetails(target); - notification.displaySuccess(i18n.getMessage("message.force.action.success")); - }); - UI.getCurrent().addWindow(confirmDialog.getWindow()); - - confirmDialog.getWindow().bringToFront(); - } - - private void confirmAndForceQuitAction(final Long actionId) { - /* Display the confirmation */ - final ConfirmationDialog confirmDialog = new ConfirmationDialog(i18n.getMessage("caption.forcequit.action.confirmbox"), - i18n.getMessage("message.forcequit.action.confirm"), i18n.getMessage(BUTTON_OK), i18n.getMessage(BUTTON_CANCEL), ok -> { - if (!ok) { - return; - } - final boolean cancelResult = forceQuitActiveAction(actionId); - if (cancelResult) { - populateAndupdateTargetDetails(target); - notification.displaySuccess(i18n.getMessage("message.forcequit.action.success")); - } else { - notification.displayValidationError(i18n.getMessage("message.forcequit.action.failed")); - } - }, FontAwesome.WARNING); - UI.getCurrent().addWindow(confirmDialog.getWindow()); - - confirmDialog.getWindow().bringToFront(); - } - - /** - * Show confirmation window and if ok then only, cancel the action. - * - * @param actionId - * as Id if the action needs to be cancelled. - */ - private void confirmAndCancelAction(final Long actionId) { - if (actionId == null) { - return; - } - - final ConfirmationDialog confirmDialog = new ConfirmationDialog(i18n.getMessage("caption.cancel.action.confirmbox"), - i18n.getMessage("message.cancel.action.confirm"), i18n.getMessage(BUTTON_OK), i18n.getMessage(BUTTON_CANCEL), ok -> { - if (!ok) { - return; - } - final boolean cancelResult = cancelActiveAction(actionId); - if (cancelResult) { - populateAndupdateTargetDetails(target); - notification.displaySuccess(i18n.getMessage("message.cancel.action.success")); - } else { - notification.displayValidationError(i18n.getMessage("message.cancel.action.failed")); - } - }); - UI.getCurrent().addWindow(confirmDialog.getWindow()); - confirmDialog.getWindow().bringToFront(); - } - - private void populateAndupdateTargetDetails(final Target target) { - // show the updated target action history details - populateTableData(target); - // update the target table and its pinning details - updateTargetAndDsTable(); - } - - // service call to cancel the active action - private boolean cancelActiveAction(final Long actionId) { - if (actionId != null) { - try { - deploymentManagement.cancelAction(actionId); - return true; - } catch (final CancelActionNotAllowedException e) { - LOG.info("Cancel action not allowed exception :{}", e); - return false; - } - } - return false; - } - - // service call to cancel the active action - private boolean forceQuitActiveAction(final Long actionId) { - if (actionId != null) { - try { - deploymentManagement.forceQuitAction(actionId); - return true; - } catch (final CancelActionNotAllowedException e) { - LOG.info("Force Cancel action not allowed exception :{}", e); - return false; - } - } - return false; - } - - private void updateTargetAndDsTable() { - eventBus.publish(this, new TargetTableEvent(BaseEntityEventType.UPDATED_ENTITY, target)); - updateDistributionTableStyle(); - } - - /** - * Update the colors of Assigned and installed distribution set in Target - * Pinning. - */ - private void updateDistributionTableStyle() { - if (managementUIState.getDistributionTableFilters().getPinnedTarget().map(TargetIdName::getTargetId) - .map(target.getId()::equals).orElse(false)) { - eventBus.publish(this, PinUnpinEvent.PIN_TARGET); - } - } - - /** - * empty the container when there is no data selected in Target Table. - */ - public void clearContainerData() { - getContainerDataSource().removeAllItems(); - } - - /** - * Set messages false. - * - * @param alreadyHasMessages - * the alreadyHasMessages to set - */ - public void setAlreadyHasMessages(final boolean alreadyHasMessages) { - this.alreadyHasMessages = alreadyHasMessages; - } - - private void restorePreviousState() { - if (managementUIState.isActionHistoryMaximized()) { - createTableContentForMax(); - } - } - - private void expandParentRow() { - if (null != managementUIState.getExpandParentActionRowId() - && !managementUIState.getExpandParentActionRowId().isEmpty()) { - for (final Object obj : managementUIState.getExpandParentActionRowId()) { - expandParentActionRow(obj); - } - } - } -} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusBeanQuery.java new file mode 100644 index 000000000..28d332c99 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusBeanQuery.java @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import static org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil.isNotNullOrEmpty; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.model.ActionStatus; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SpringContextHelper; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery; +import org.vaadin.addons.lazyquerycontainer.QueryDefinition; + +/** + * Simple implementation of generics bean query which dynamically loads + * {@link ProxyActionStatus} batch of beans. + */ +public class ActionStatusBeanQuery extends AbstractBeanQuery { + private static final long serialVersionUID = 1L; + + private Sort sort = new Sort(Direction.DESC, "id"); + private transient DeploymentManagement deploymentManagement; + + private Long currentSelectedActionId; + private transient Page firstPageActionStates; + + /** + * Parametric Constructor. + * + * @param definition + * QueryDefinition contains the query properties. + * @param queryConfig + * Implementation specific configuration. + * @param sortPropertyIds + * The properties participating in sort. + * @param sortStates + * The ascending or descending state of sort properties. + */ + public ActionStatusBeanQuery(final QueryDefinition definition, final Map queryConfig, + final Object[] sortPropertyIds, final boolean[] sortStates) { + super(definition, queryConfig, sortPropertyIds, sortStates); + + if (isNotNullOrEmpty(queryConfig)) { + currentSelectedActionId = (Long) queryConfig.get(SPUIDefinitions.ACTIONSTATES_BY_ACTION); + } + + if (sortStates.length > 0) { + // Initialize sort + sort = new Sort(sortStates[0] ? Direction.ASC : Direction.DESC, (String) sortPropertyIds[0]); + // Add sort + for (int distId = 1; distId < sortPropertyIds.length; distId++) { + sort.and(new Sort(sortStates[distId] ? Direction.ASC : Direction.DESC, + (String) sortPropertyIds[distId])); + } + } + } + + @Override + protected ProxyActionStatus constructBean() { + return new ProxyActionStatus(); + } + + @Override + protected List loadBeans(final int startIndex, final int count) { + Page actionBeans; + if (startIndex == 0 && firstPageActionStates != null) { + actionBeans = firstPageActionStates; + } else { + actionBeans = getDeploymentManagement() + .findActionStatusByAction( + new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, SPUIDefinitions.PAGE_SIZE, sort), + currentSelectedActionId); + } + return createProxyActionStates(actionBeans); + } + + /** + * Creates a list of {@link ProxyActionStatus} for presentation layer from + * page of {@link ActionStatus}. + * + * @param actionBeans + * page of {@link ActionStatus} + * @return list of {@link ProxyActionStatus} + */ + private static List createProxyActionStates(final Page actionStatusBeans) { + final List proxyActionStates = new ArrayList<>(); + for (final ActionStatus actionStatus : actionStatusBeans) { + final ProxyActionStatus proxyAS = new ProxyActionStatus(); + proxyAS.setCreatedAt(actionStatus.getCreatedAt()); + proxyAS.setStatus(actionStatus.getStatus()); + proxyAS.setId(actionStatus.getId()); + + proxyActionStates.add(proxyAS); + } + return proxyActionStates; + } + + /* + * (non-Javadoc) + * + * @see + * org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery#saveBeans(java + * .util.List, java.util.List, java.util.List) + */ + @Override + protected void saveBeans(List addedBeans, List modifiedBeans, + List removedBeans) { + // CRUD operations on Target will be done through repository methods + } + + @Override + public int size() { + long size = 0; + + if (currentSelectedActionId != null) { + firstPageActionStates = getDeploymentManagement().findActionStatusByAction( + new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), currentSelectedActionId); + size = firstPageActionStates.getTotalElements(); + } + if (size > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + + return (int) size; + } + + /** + * Lazy gets deploymentManagement. + * + * @return the deploymentManagement + */ + public DeploymentManagement getDeploymentManagement() { + if (null == deploymentManagement) { + deploymentManagement = SpringContextHelper.getBean(DeploymentManagement.class); + } + return deploymentManagement; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusGrid.java new file mode 100644 index 000000000..a7e18f855 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusGrid.java @@ -0,0 +1,159 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import java.util.Map; + +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.ui.common.grid.AbstractGrid; +import org.eclipse.hawkbit.ui.customrenderers.renderers.HtmlLabelRenderer; +import org.eclipse.hawkbit.ui.management.actionhistory.ActionHistoryGrid.LabelConfig; +import org.eclipse.hawkbit.ui.rollout.StatusFontIcon; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; +import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; +import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition; +import org.vaadin.spring.events.EventBus.UIEventBus; + +import com.google.common.collect.Maps; + +/** + * This grid presents the action states for a selected action. + */ +public class ActionStatusGrid extends AbstractGrid { + private static final long serialVersionUID = 1L; + + private static final String[] leftAlignedColumns = new String[] { ProxyActionStatus.PXY_AS_CREATED_AT }; + + private static final String[] centerAlignedColumns = new String[] { ProxyActionStatus.PXY_AS_STATUS }; + + private final AlignCellStyleGenerator alignGenerator; + private final ModifiedTimeTooltipGenerator modTimetooltipGenerator; + + private final Map states; + + private final BeanQueryFactory targetQF = new BeanQueryFactory<>( + ActionStatusBeanQuery.class); + + /** + * Constructor. + * + * @param i18n + * @param eventBus + */ + protected ActionStatusGrid(final VaadinMessageSource i18n, final UIEventBus eventBus) { + super(i18n, eventBus, null); + + setSingleSelectionSupport(new SingleSelectionSupport()); + setDetailsSupport(new DetailsSupport()); + + final LabelConfig conf = new ActionHistoryGrid.LabelConfig(); + states = conf.createStatusLabelConfig(i18n, UIComponentIdProvider.ACTION_STATUS_GRID_STATUS_LABEL_ID); + alignGenerator = new AlignCellStyleGenerator(leftAlignedColumns, centerAlignedColumns, null); + modTimetooltipGenerator = new ModifiedTimeTooltipGenerator(ProxyActionStatus.PXY_AS_CREATED_AT); + + init(); + } + + @Override + protected LazyQueryContainer createContainer() { + configureQueryFactory(); + return new LazyQueryContainer( + new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, ProxyActionStatus.PXY_AS_ID), targetQF); + } + + @Override + public void refreshContainer() { + configureQueryFactory(); + super.refreshContainer(); + } + + protected void configureQueryFactory() { + // ADD all the filters to the query config + final Map queryConfig = Maps.newHashMapWithExpectedSize(1); + queryConfig.put(SPUIDefinitions.ACTIONSTATES_BY_ACTION, getDetailsSupport().getMasterDataId()); + // Create ActionBeanQuery factory with the query config. + targetQF.setQueryConfiguration(queryConfig); + } + + /** + * Gets type-save access to LazyQueryContainer. + * + * @return LazyQueryContainer + */ + private LazyQueryContainer getLazyQueryContainer() { + return (LazyQueryContainer) getContainerDataSource(); + } + + @Override + protected void addContainerProperties() { + final LazyQueryContainer lqContainer = getLazyQueryContainer(); + lqContainer.addContainerProperty(ProxyActionStatus.PXY_AS_CREATED_AT, Long.class, null, true, true); + lqContainer.addContainerProperty(ProxyActionStatus.PXY_AS_STATUS, Action.Status.class, null, true, false); + } + + @Override + protected void setColumnExpandRatio() { + getColumn(ProxyActionStatus.PXY_AS_STATUS).setMinimumWidth(53); + getColumn(ProxyActionStatus.PXY_AS_STATUS).setMaximumWidth(55); + getColumn(ProxyActionStatus.PXY_AS_CREATED_AT).setMinimumWidth(100); + getColumn(ProxyActionStatus.PXY_AS_CREATED_AT).setMaximumWidth(400); + } + + @Override + protected void setColumnHeaderNames() { + getColumn(ProxyActionStatus.PXY_AS_STATUS).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_STATUS); + getColumn(ProxyActionStatus.PXY_AS_CREATED_AT).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_DATETIME); + } + + @Override + protected String getGridId() { + return UIComponentIdProvider.ACTION_HISTORY_DETAILS_GRID_ID; + } + + @Override + protected void setColumnProperties() { + clearSortOrder(); + setColumns(ProxyActionStatus.PXY_AS_STATUS, ProxyActionStatus.PXY_AS_CREATED_AT); + alignColumns(); + } + + @Override + protected void addColumnRenderes() { + getColumn(ProxyActionStatus.PXY_AS_STATUS).setRenderer(new HtmlLabelRenderer(), + new HtmlStatusLabelConverter(this::createStatusLabelMetadata)); + getColumn(ProxyActionStatus.PXY_AS_CREATED_AT).setConverter(new LongToFormattedDateStringConverter()); + } + + private StatusFontIcon createStatusLabelMetadata(final Action.Status status) { + return states.get(status); + } + + @Override + protected void setHiddenColumns() { + getColumn(ProxyActionStatus.PXY_AS_STATUS).setHidable(false); + getColumn(ProxyActionStatus.PXY_AS_CREATED_AT).setHidable(false); + } + + /** + * Sets the alignment cell-style-generator that handles the alignment for + * the grid cells. + */ + private void alignColumns() { + setCellStyleGenerator(alignGenerator); + } + + @Override + protected CellDescriptionGenerator getDescriptionGenerator() { + return modTimetooltipGenerator; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusLayout.java new file mode 100644 index 000000000..f0343b540 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusLayout.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import org.eclipse.hawkbit.ui.common.grid.AbstractGridComponentLayout; +import org.eclipse.hawkbit.ui.common.grid.DefaultGridHeader; +import org.eclipse.hawkbit.ui.management.state.ManagementUIState; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.vaadin.spring.events.EventBus.UIEventBus; + +/** + * Layout responsible for action-states-grid and the corresponding header. + */ +public class ActionStatusLayout extends AbstractGridComponentLayout { + private static final long serialVersionUID = 1L; + + protected final ManagementUIState managementUIState; + + /** + * Constructor. + * + * @param i18n + * @param eventBus + * @param managementUIState + */ + public ActionStatusLayout(final VaadinMessageSource i18n, final UIEventBus eventBus, + final ManagementUIState managementUIState) { + super(i18n, eventBus); + this.managementUIState = managementUIState; + init(); + } + + @Override + public DefaultGridHeader createGridHeader() { + return new DefaultGridHeader(managementUIState, "Action States").init(); + } + + @Override + public ActionStatusGrid createGrid() { + return new ActionStatusGrid(i18n, eventBus); + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusMsgBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusMsgBeanQuery.java new file mode 100644 index 000000000..6e37da97b --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusMsgBeanQuery.java @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import static org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil.isNotNullOrEmpty; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.model.ActionStatus; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SpringContextHelper; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery; +import org.vaadin.addons.lazyquerycontainer.QueryDefinition; + +import com.google.common.base.Strings; + +/** + * Simple implementation of generic bean query which dynamically loads + * {@link ProxyMessage} batch of beans. + */ +public class ActionStatusMsgBeanQuery extends AbstractBeanQuery { + private static final long serialVersionUID = 1L; + + private Sort sort = new Sort(Direction.DESC, "id"); + private transient DeploymentManagement deploymentManagement; + + private Long currentSelectedActionStatusId; + private String noMessageText; + private transient Page firstPageMessages; + + /** + * Parametric Constructor. + * + * @param definition + * QueryDefinition contains the query properties. + * @param queryConfig + * Implementation specific configuration. + * @param sortPropertyIds + * The properties participating in sort. + * @param sortStates + * The ascending or descending state of sort properties. + */ + public ActionStatusMsgBeanQuery(final QueryDefinition definition, final Map queryConfig, + final Object[] sortPropertyIds, final boolean[] sortStates) { + super(definition, queryConfig, sortPropertyIds, sortStates); + + if (isNotNullOrEmpty(queryConfig)) { + currentSelectedActionStatusId = (Long) queryConfig.get(SPUIDefinitions.MESSAGES_BY_ACTIONSTATUS); + noMessageText = (String) queryConfig.get(SPUIDefinitions.NO_MSG_PROXY); + } + + if (sortStates.length > 0) { + // Initialize sort + sort = new Sort(sortStates[0] ? Direction.ASC : Direction.DESC, (String) sortPropertyIds[0]); + // Add sort + for (int distId = 1; distId < sortPropertyIds.length; distId++) { + sort.and(new Sort(sortStates[distId] ? Direction.ASC : Direction.DESC, + (String) sortPropertyIds[distId])); + } + } + } + + @Override + protected ProxyMessage constructBean() { + return new ProxyMessage(); + } + + @Override + protected List loadBeans(final int startIndex, final int count) { + Page actionBeans; + if (startIndex == 0 && firstPageMessages != null) { + actionBeans = firstPageMessages; + } else { + actionBeans = getDeploymentManagement().findMessagesByActionStatusId( + new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, SPUIDefinitions.PAGE_SIZE, sort), + currentSelectedActionStatusId); + } + return createProxyMessages(actionBeans); + } + + /** + * Creates a list of {@link ProxyActionStatus} for presentation layer from + * page of {@link ActionStatus}. + * + * @param actionBeans + * page of {@link ActionStatus} + * @return list of {@link ProxyActionStatus} + */ + private List createProxyMessages(final Page messages) { + final List proxyMsgs = new ArrayList<>(messages.getNumberOfElements()); + + Long idx = messages.getNumber() * ((long) messages.getSize()); + for (final String msg : messages) { + idx++; + final ProxyMessage proxyMsg = new ProxyMessage(); + proxyMsg.setMessage(msg); + proxyMsg.setId(String.valueOf(idx)); + proxyMsgs.add(proxyMsg); + } + + if (messages.getTotalElements() == 1L && Strings.isNullOrEmpty(proxyMsgs.get(0).getMessage())) { + proxyMsgs.get(0).setMessage(noMessageText); + } + + return proxyMsgs; + } + + /* + * (non-Javadoc) + * + * @see + * org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery#saveBeans(java + * .util.List, java.util.List, java.util.List) + */ + @Override + protected void saveBeans(List addedBeans, List modifiedBeans, + List removedBeans) { + // CRUD operations on Target will be done through repository methods + } + + @Override + public int size() { + long size = 0; + + if (currentSelectedActionStatusId != null) { + firstPageMessages = getDeploymentManagement().findMessagesByActionStatusId( + new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), currentSelectedActionStatusId); + size = firstPageMessages.getTotalElements(); + } + if (size > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + + return (int) size; + } + + /** + * Lazy gets deploymentManagement. + * + * @return the deploymentManagement + */ + public DeploymentManagement getDeploymentManagement() { + if (null == deploymentManagement) { + deploymentManagement = SpringContextHelper.getBean(DeploymentManagement.class); + } + return deploymentManagement; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusMsgGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusMsgGrid.java new file mode 100644 index 000000000..9af96361f --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusMsgGrid.java @@ -0,0 +1,210 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import java.util.Map; + +import org.eclipse.hawkbit.ui.common.grid.AbstractGrid; +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.VaadinMessageSource; +import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; +import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; +import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition; +import org.vaadin.spring.events.EventBus.UIEventBus; + +import com.google.common.collect.Maps; +import com.vaadin.data.Item; +import com.vaadin.event.ItemClickEvent; +import com.vaadin.event.ItemClickEvent.ItemClickListener; +import com.vaadin.ui.Component; +import com.vaadin.ui.TextArea; +import com.vaadin.ui.themes.ValoTheme; + +/** + * This grid presents the messages for a selected action-status. + */ +public class ActionStatusMsgGrid extends AbstractGrid { + private static final long serialVersionUID = 1L; + + private static final String[] rightAlignedColumns = new String[] { ProxyMessage.PXY_MSG_ID }; + + private final String noMsgText; + + private final AlignCellStyleGenerator alignGenerator; + + private final BeanQueryFactory targetQF = new BeanQueryFactory<>( + ActionStatusMsgBeanQuery.class); + + /** + * Constructor. + * + * @param i18n + * @param eventBus + */ + protected ActionStatusMsgGrid(final VaadinMessageSource i18n, final UIEventBus eventBus) { + super(i18n, eventBus, null); + noMsgText = createNoMessageProxy(i18n); + + setSingleSelectionSupport(new SingleSelectionSupport()); + setDetailsSupport(new DetailsSupport()); + + alignGenerator = new AlignCellStyleGenerator(null, null, rightAlignedColumns); + addStyleName(SPUIStyleDefinitions.ACTION_HISTORY_MESSAGE_GRID); + + setDetailsGenerator(new MessageDetailsGenerator()); + + this.addItemClickListener(new ItemClickListener() { + private static final long serialVersionUID = 1L; + + @Override + public void itemClick(final ItemClickEvent event) { + final Object itemId = event.getItemId(); + setDetailsVisible(itemId, !isDetailsVisible(itemId)); + } + }); + + init(); + } + + @Override + protected LazyQueryContainer createContainer() { + configureQueryFactory(); + return new LazyQueryContainer(new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, null), targetQF); + } + + @Override + public void refreshContainer() { + for (final Object itemId : getContainerDataSource().getItemIds()) { + setDetailsVisible(itemId, false); + } + configureQueryFactory(); + super.refreshContainer(); + } + + protected void configureQueryFactory() { + // ADD all the filters to the query config + final Map queryConfig = Maps.newHashMapWithExpectedSize(2); + queryConfig.put(SPUIDefinitions.MESSAGES_BY_ACTIONSTATUS, getDetailsSupport().getMasterDataId()); + queryConfig.put(SPUIDefinitions.NO_MSG_PROXY, noMsgText); + // Create ActionBeanQuery factory with the query config. + targetQF.setQueryConfiguration(queryConfig); + } + + /** + * Gets type-save access to LazyQueryContainer. + * + * @return LazyQueryContainer + */ + private LazyQueryContainer getLazyQueryContainer() { + return (LazyQueryContainer) getContainerDataSource(); + } + + @Override + protected void addContainerProperties() { + getLazyQueryContainer().addContainerProperty(ProxyMessage.PXY_MSG_ID, String.class, null, true, false); + getLazyQueryContainer().addContainerProperty(ProxyMessage.PXY_MSG_VALUE, String.class, null, true, false); + } + + @Override + protected void setColumnExpandRatio() { + getColumn(ProxyMessage.PXY_MSG_ID).setExpandRatio(0); + getColumn(ProxyMessage.PXY_MSG_VALUE).setExpandRatio(1); + } + + @Override + protected void setColumnHeaderNames() { + getColumn(ProxyMessage.PXY_MSG_ID).setHeaderCaption("##"); + getColumn(ProxyMessage.PXY_MSG_VALUE).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_MSGS); + } + + @Override + protected String getGridId() { + return UIComponentIdProvider.ACTION_HISTORY_MESSAGE_GRID_ID; + } + + @Override + protected void setColumnProperties() { + clearSortOrder(); + setColumns(ProxyMessage.PXY_MSG_ID, ProxyMessage.PXY_MSG_VALUE); + setFrozenColumnCount(2); + alignColumns(); + } + + @Override + protected void addColumnRenderes() { + // no specific column renderers + } + + @Override + protected void setHiddenColumns() { + getColumn(ProxyMessage.PXY_MSG_ID).setHidable(false); + getColumn(ProxyMessage.PXY_MSG_VALUE).setHidable(false); + } + + @Override + protected CellDescriptionGenerator getDescriptionGenerator() { + return null; + } + + /** + * Sets the alignment cell-style-generator that handles the alignment for + * the grid cells. + */ + private void alignColumns() { + setCellStyleGenerator(alignGenerator); + } + + /** + * Creates the default text when no message is available for action-status + * + * @param i18n + * @return default text + */ + private static String createNoMessageProxy(final VaadinMessageSource i18n) { + return i18n.getMessage("message.no.available"); + } + + protected class MessageDetailsGenerator implements DetailsGenerator { + private static final long serialVersionUID = 1L; + + @Override + public Component getDetails(final RowReference rowReference) { + // Find the bean to generate details for + final Item item = rowReference.getItem(); + final String message = (String) item.getItemProperty(ProxyMessage.PXY_MSG_VALUE).getValue(); + + final TextArea textArea = new TextArea(); + textArea.addStyleName(ValoTheme.TEXTAREA_BORDERLESS); + textArea.addStyleName(ValoTheme.TEXTAREA_TINY); + textArea.addStyleName("inline-icon"); + textArea.setHeight(120, Unit.PIXELS); + textArea.setWidth(100, Unit.PERCENTAGE); + textArea.setValue(message); + textArea.setReadOnly(Boolean.TRUE); + return textArea; + } + } + + /** + * CellStyleGenerator that concerns about cutting text. + */ + protected static class TextCutCellStyleGenerator implements CellStyleGenerator { + private static final long serialVersionUID = 1L; + + @Override + public String getStyle(final CellReference cellReference) { + if (ProxyMessage.PXY_MSG_VALUE.equals(cellReference.getPropertyId())) { + return "text-cut"; + } + return null; + } + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusMsgLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusMsgLayout.java new file mode 100644 index 000000000..40e9c36a9 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionStatusMsgLayout.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import org.eclipse.hawkbit.ui.common.grid.AbstractGridComponentLayout; +import org.eclipse.hawkbit.ui.common.grid.DefaultGridHeader; +import org.eclipse.hawkbit.ui.management.state.ManagementUIState; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.vaadin.spring.events.EventBus.UIEventBus; + +/** + * Layout responsible for messages-grid and the corresponding header. + */ +public class ActionStatusMsgLayout extends AbstractGridComponentLayout { + private static final long serialVersionUID = 1L; + + protected final ManagementUIState managementUIState; + + /** + * Constructor. + * + * @param i18n + * @param eventBus + * @param managementUIState + */ + public ActionStatusMsgLayout(final VaadinMessageSource i18n, final UIEventBus eventBus, + final ManagementUIState managementUIState) { + super(i18n, eventBus); + this.managementUIState = managementUIState; + init(); + } + + @Override + public DefaultGridHeader createGridHeader() { + return new DefaultGridHeader(managementUIState, "Messages").init(); + } + + @Override + public ActionStatusMsgGrid createGrid() { + return new ActionStatusMsgGrid(i18n, eventBus); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/HtmlStatusLabelConverter.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/HtmlStatusLabelConverter.java new file mode 100644 index 000000000..f9f6c4b1e --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/HtmlStatusLabelConverter.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.ui.customrenderers.renderers.AbstractHtmlLabelConverter; + +/** + * Concrete html-label converter that handles Action.Status. + */ +public class HtmlStatusLabelConverter extends AbstractHtmlLabelConverter { + + private static final long serialVersionUID = 1L; + + /** + * Constructor that sets the appropriate adapter. + * + * @param adapter + * adapts Action.Status to String + */ + public HtmlStatusLabelConverter(LabelAdapter adapter) { + this.addAdapter(adapter); + } + + @Override + public Class getModelType() { + return Action.Status.class; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ProxyAction.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ProxyAction.java new file mode 100644 index 000000000..5f6e07dce --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ProxyAction.java @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import java.io.Serializable; + +import org.eclipse.hawkbit.repository.model.Action; + +/** + * Proxy for {@link Action} + */ +public class ProxyAction implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String PXY_ACTION_STATUS = "status"; + public static final String PXY_ACTION_IS_ACTIVE = "isActive"; + public static final String PXY_ACTION_IS_ACTIVE_DECO = "isActiveDecoration"; + public static final String PXY_ACTION_ID = "id"; + public static final String PXY_ACTION_DS_NAME_VERSION = "dsNameVersion"; + public static final String PXY_ACTION = "action"; + public static final String PXY_ACTION_LAST_MODIFIED_AT = "lastModifiedAt"; + public static final String PXY_ACTION_ROLLOUT_NAME = "rolloutName"; + + private Action.Status status; + private boolean isActive; + private IsActiveDecoration isActiveDecoration; + private Long id; + private String dsNameVersion; + private Action action; + private Long lastModifiedAt; + private String rolloutName; + + /** + * Get id for the entry. + * + * @return id for the entry. + */ + public Long getId() { + return id; + } + + /** + * Set the id for the entry. + * + * @param id + * of the action entry. + */ + public void setId(Long id) { + this.id = id; + } + + /** + * Gets the status literal. + * + * @return status literal + */ + public Action.Status getStatus() { + return status; + } + + /** + * Sets the status literal. + * + * @param status + * literal + */ + public void setStatus(Action.Status status) { + this.status = status; + } + + /** + * Flag that indicates if the action is active. + * + * @return true if the action is active, otherwise + * false + */ + public boolean isActive() { + return isActive; + } + + /** + * Sets the flag that indicates if the action is active. + * + * @param isActive + * true if the action is active, otherwise + * false + */ + public void setActive(boolean isActive) { + this.isActive = isActive; + } + + /** + * Gets a pre-calculated literal combining ProxyAction#isActive + * and ProxyAction#getStatus states. + * + * @return pre-calculated literal + */ + public IsActiveDecoration getIsActiveDecoration() { + return isActiveDecoration; + } + + /** + * Sets the pre-calculated literal combining + * ProxyAction#isActive and ProxyAction#getStatus + * states. + * + * @param isActiveDecoration + * pre-calculated literal + */ + public void setIsActiveDecoration(IsActiveDecoration isActiveDecoration) { + this.isActiveDecoration = isActiveDecoration; + } + + /** + * Pre-calculated value that is set up by distribution set name and version. + * + * @return pre-calculated value combining name and version + */ + public String getDsNameVersion() { + return dsNameVersion; + } + + /** + * Sets the pre-calculated value combining distribution set name and + * version. + * + * @param dsNameVersion + * combined value + */ + public void setDsNameVersion(String dsNameVersion) { + this.dsNameVersion = dsNameVersion; + } + + /** + * Gets the action to be evaluated by generators of virtual properties. + * + * @return action + */ + public Action getAction() { + return action; + } + + /** + * Sets the action to be evaluated by generators of virtual properties. + * + * @param action + */ + public void setAction(Action action) { + this.action = action; + } + + /** + * Get raw long-value for lastModifiedAt-date. + * + * @return raw long-value for lastModifiedAt-date + */ + public Long getLastModifiedAt() { + return lastModifiedAt; + } + + /** + * Set raw long-value for lastModifiedAt-date. + * + * @param lastModifiedAt + * raw long-value for lastModifiedAt-date + */ + public void setLastModifiedAt(Long lastModifiedAt) { + this.lastModifiedAt = lastModifiedAt; + } + + /** + * Gets the rolloutName. + * + * @return rolloutName + */ + public String getRolloutName() { + return rolloutName; + } + + /** + * Sets the rolloutName. + * + * @param rolloutName + */ + public void setRolloutName(String rolloutName) { + this.rolloutName = rolloutName; + } + + /** + * Pre-calculated decoration value combining + * ProxyAction#isActive and ProxyAction#getStatus + * states. + */ + public enum IsActiveDecoration { + /** + * Active label decoration type for {@code ProxyAction#isActive()==true} + */ + ACTIVE, + + /** + * Active label decoration type for + * {@code ProxyAction#isActive()==false} + */ + IN_ACTIVE, + + /** + * Active label decoration type for {@code ProxyAction#isActive()==true} + * AND {@code ProxyAction#getStatus()==Action.Status.ERROR} + */ + IN_ACTIVE_ERROR, + + /** + * {@code ProxyAction#getStatus()==Action.Status.SCHEDULED} + */ + SCHEDULED, + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ProxyActionStatus.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ProxyActionStatus.java new file mode 100644 index 000000000..1296cca23 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ProxyActionStatus.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import java.io.Serializable; + +import org.eclipse.hawkbit.repository.model.Action; + +/** + * Proxy for {@link ActionStatus} + */ +public class ProxyActionStatus implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String PXY_AS_STATUS = "status"; + public static final String PXY_AS_CREATED_AT = "createdAt"; + public static final String PXY_AS_ID = "id"; + + private Long id; + private Action.Status status; + private Long createdAt; + + /** + * Get id for the entry. + * + * @return id for the entry. + */ + public Long getId() { + return id; + } + + /** + * Set the id for the entry. + * + * @param id + * of the status entry. + */ + public void setId(Long id) { + this.id = id; + } + + /** + * Gets the status literal. + * + * @return status literal + */ + public Action.Status getStatus() { + return status; + } + + /** + * Sets the status literal. + * + * @param status + * literal + */ + public void setStatus(Action.Status status) { + this.status = status; + } + + /** + * Get raw long-value for createdAt-date. + * + * @return raw long-value for createdAt-date + */ + public Long getCreatedAt() { + return createdAt; + } + + /** + * Set raw long-value for createdAt-date. + * + * @param createdAt + * raw long-value for createdAt-date + */ + public void setCreatedAt(Long createdAt) { + this.createdAt = createdAt; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ProxyMessage.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ProxyMessage.java new file mode 100644 index 000000000..c6bb3a051 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ProxyMessage.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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.actionhistory; + +import java.io.Serializable; + +import org.eclipse.hawkbit.repository.model.ActionStatus; + +/** + * Proxy for an entry of {@link ActionStatus#getMessages()} + */ +public class ProxyMessage implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String PXY_MSG_ID = "id"; + public static final String PXY_MSG_VALUE = "message"; + + private String id; + private String message; + + /** + * Get id for the entry. + * + * @return id for the entry. + */ + public String getId() { + return id; + } + + /** + * Set the id for the entry. + * + * @param id + * of the message entry. + */ + public void setId(String id) { + this.id = id; + } + + /** + * Get message value. + * + * @return message value + */ + public String getMessage() { + return message; + } + + /** + * Set message value. + * + * @param message + * value + */ + public void setMessage(String message) { + this.message = message; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/menu/DashboardMenu.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/menu/DashboardMenu.java index db5babfd1..6da59fb5f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/menu/DashboardMenu.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/menu/DashboardMenu.java @@ -25,15 +25,14 @@ import org.eclipse.hawkbit.ui.UiProperties; import org.eclipse.hawkbit.ui.common.UserDetailsFormatter; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.menu.DashboardEvent.PostViewChangeEvent; -import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.springframework.beans.factory.annotation.Autowired; import com.vaadin.server.FontAwesome; import com.vaadin.server.Page; import com.vaadin.server.Resource; import com.vaadin.server.ThemeResource; -import com.vaadin.shared.ui.label.ContentMode; import com.vaadin.spring.annotation.SpringComponent; import com.vaadin.spring.annotation.UIScope; import com.vaadin.ui.Alignment; @@ -43,7 +42,6 @@ import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.Component; import com.vaadin.ui.CssLayout; import com.vaadin.ui.CustomComponent; -import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Link; import com.vaadin.ui.MenuBar; @@ -82,8 +80,9 @@ public final class DashboardMenu extends CustomComponent { private boolean accessibleViewsEmpty; @Autowired - DashboardMenu(final VaadinMessageSource i18n, final UiProperties uiProperties, final HawkbitServerProperties serverProperties, - final PermissionService permissionService, final List dashboardVaadinViews) { + DashboardMenu(final VaadinMessageSource i18n, final UiProperties uiProperties, + final HawkbitServerProperties serverProperties, final PermissionService permissionService, + final List dashboardVaadinViews) { this.i18n = i18n; this.uiProperties = uiProperties; this.serverProperties = serverProperties; @@ -109,7 +108,6 @@ public final class DashboardMenu extends CustomComponent { final VerticalLayout dashboardMenuLayout = new VerticalLayout(); dashboardMenuLayout.setSizeFull(); final VerticalLayout menuContent = getMenuLayout(); - menuContent.addComponent(buildTitle()); menuContent.addComponent(buildUserMenu()); menuContent.addComponent(buildToggleButton()); @@ -137,15 +135,6 @@ public final class DashboardMenu extends CustomComponent { return menuContent; } - private Component buildTitle() { - final Label logo = new Label("" + i18n.getMessage("menu.title") + "", ContentMode.HTML); - logo.setSizeUndefined(); - final HorizontalLayout logoWrapper = new HorizontalLayout(logo); - logoWrapper.setComponentAlignment(logo, Alignment.TOP_CENTER); - logoWrapper.addStyleName("valo-menu-title"); - return logoWrapper; - } - private VerticalLayout buildLinksAndVersion() { final VerticalLayout links = new VerticalLayout(); links.setSpacing(true); @@ -174,8 +163,8 @@ public final class DashboardMenu extends CustomComponent { if (!uiProperties.getLinks().getSupport().isEmpty()) { final Link supportLink = SPUIComponentProvider.getLink(UIComponentIdProvider.LINK_SUPPORT, - i18n.getMessage("link.support.name"), uiProperties.getLinks().getSupport(), FontAwesome.ENVELOPE_O, "", - linkStyle); + i18n.getMessage("link.support.name"), uiProperties.getLinks().getSupport(), FontAwesome.ENVELOPE_O, + "", linkStyle); supportLink.setDescription(i18n.getMessage("link.support.name")); supportLink.setSizeFull(); links.addComponent(supportLink); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/StatusFontIcon.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/StatusFontIcon.java index cfd1e63dd..0289d4e75 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/StatusFontIcon.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/StatusFontIcon.java @@ -8,35 +8,140 @@ */ package org.eclipse.hawkbit.ui.rollout; -import org.eclipse.hawkbit.repository.model.Rollout; -import org.eclipse.hawkbit.repository.model.RolloutGroup; +import java.io.Serializable; -import com.google.gwt.i18n.client.AutoDirectionHandler.Target; import com.vaadin.server.FontAwesome; /** - * - * Helper class which holds the details of font icon to be displayed for - * {@link Rollout}/ {@link RolloutGroup} / Rollout {@link Target} - * - * + * Helper class which holds the details of font icon to be displayed as + * label/button in grid: + *

+ * RolloutListGrid / RolloutGroupListGrid / + * RolloutGroupTargetsListGrid / ActionHistoryGrid */ -public class StatusFontIcon { - final FontAwesome fontIcon; - final String style; +public class StatusFontIcon implements Serializable { + /** serialVersionUID. */ + private static final long serialVersionUID = 1L; + private FontAwesome fontIcon; + private String style; + private String title; + private String id; + private boolean disabled; - public StatusFontIcon(final FontAwesome fontIcon, final String style) { - super(); - this.fontIcon = fontIcon; - this.style = style; + /** + * NOTE: This constructor is used for (de-)serialization only!!! + */ + public StatusFontIcon() { + // empty } + /** + * Constructor to create icon metadata object. + * + * @param fontIcon + * the font representing the icon + * @param style + * the style + */ + public StatusFontIcon(final FontAwesome fontIcon, final String style) { + this(fontIcon, style, "", "", false); + } + + /** + * Constructor to create icon metadata object. + * + * @param fontIcon + * the font representing the icon + * @param style + * the style + * @param title + * the title shown as tooltip + */ + public StatusFontIcon(final FontAwesome fontIcon, final String style, final String title) { + this(fontIcon, style, title, "", false); + } + + /** + * Constructor to create icon metadata object. + * + * @param fontIcon + * the font representing the icon + * @param style + * the style + * @param title + * the title shown as tooltip + * @param id + * the id for direct access + */ + public StatusFontIcon(final FontAwesome fontIcon, final String style, final String title, final String id) { + this(fontIcon, style, title, id, false); + } + + /** + * Constructor to create icon metadata object. + * + * @param fontIcon + * the font representing the icon + * @param style + * the style + * @param title + * the title shown as tooltip + * @param id + * the id for direct access + * @param disabled + * disabled-state of the icon + */ + public StatusFontIcon(final FontAwesome fontIcon, final String style, final String title, final String id, + final boolean disabled) { + this.fontIcon = fontIcon; + this.style = style; + this.title = title; + this.id = id; + this.disabled = disabled; + } + + /** + * Gets the font representing the icon. + * + * @return the font representing the icon + */ public FontAwesome getFontIcon() { return fontIcon; } + /** + * Gets the style. + * + * @return the style + */ public String getStyle() { return style; } + /** + * Gets the title shown as tooltip. + * + * @return the title shown as tooltip. + */ + public String getTitle() { + return title; + } + + /** + * Gets the id for direct access. + * + * @return the id for direct access. + */ + public String getId() { + return id; + } + + /** + * Gets the disabled-state of the icon. + * + * @return the disabled-state of the icon. + */ + public boolean isDisabled() { + return disabled; + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListGrid.java index 5defc0773..1b788f757 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListGrid.java @@ -70,7 +70,7 @@ import com.vaadin.ui.renderers.HtmlRenderer; /** * Rollout list grid component. */ -public class RolloutListGrid extends AbstractGrid { +public class RolloutListGrid extends AbstractGrid { private static final long serialVersionUID = 1L; @@ -132,6 +132,8 @@ public class RolloutListGrid extends AbstractGrid { uiNotification, uiProperties, entityFactory, i18n, eventBus, targetFilterQueryManagement); this.uiNotification = uiNotification; this.rolloutUIState = rolloutUIState; + + init(); } /** @@ -211,7 +213,7 @@ public class RolloutListGrid extends AbstractGrid { } @Override - protected Container createContainer() { + protected LazyQueryContainer createContainer() { final BeanQueryFactory rolloutQf = new BeanQueryFactory<>(RolloutBeanQuery.class); return new LazyQueryContainer( new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, SPUILabelDefinitions.VAR_ID), rolloutQf); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListView.java index c0e14a253..b61b8561a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListView.java @@ -14,43 +14,61 @@ import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.UiProperties; -import org.eclipse.hawkbit.ui.common.grid.AbstractGridLayout; +import org.eclipse.hawkbit.ui.common.grid.AbstractGrid; +import org.eclipse.hawkbit.ui.common.grid.AbstractGridComponentLayout; import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.UINotification; +import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; import org.vaadin.spring.events.EventBus.UIEventBus; -import com.vaadin.ui.Label; +import com.vaadin.ui.AbstractOrderedLayout; /** * Rollout list view. */ -public class RolloutListView extends AbstractGridLayout { - +public class RolloutListView extends AbstractGridComponentLayout { private static final long serialVersionUID = -2703552177439393208L; + private final transient RolloutManagement rolloutManagement; + private final transient TargetManagement targetManagement; + private final transient EntityFactory entityFactory; + private final transient TargetFilterQueryManagement targetFilterQueryManagement; + + private final SpPermissionChecker permissionChecker; + private final RolloutUIState rolloutUIState; + private final UINotification uiNotification; + private final UiProperties uiProperties; + + public RolloutListView(final SpPermissionChecker permissionChecker, final RolloutUIState rolloutUIState, final UIEventBus eventBus, final RolloutManagement rolloutManagement, final TargetManagement targetManagement, final UINotification uiNotification, final UiProperties uiProperties, final EntityFactory entityFactory, final VaadinMessageSource i18n, final TargetFilterQueryManagement targetFilterQueryManagement) { - super(new RolloutListHeader(permissionChecker, rolloutUIState, eventBus, rolloutManagement, targetManagement, - uiNotification, uiProperties, entityFactory, i18n, targetFilterQueryManagement), - new RolloutListGrid(i18n, eventBus, rolloutManagement, uiNotification, rolloutUIState, - permissionChecker, targetManagement, entityFactory, uiProperties, targetFilterQueryManagement)); + super(i18n, eventBus); + this.permissionChecker = permissionChecker; + this.rolloutUIState = rolloutUIState; + this.rolloutManagement = rolloutManagement; + this.targetManagement = targetManagement; + this.uiNotification = uiNotification; + this.uiProperties = uiProperties; + this.entityFactory = entityFactory; + this.targetFilterQueryManagement = targetFilterQueryManagement; - buildLayout(); + init(); } @Override - protected boolean hasCountMessage() { - return false; + public AbstractOrderedLayout createGridHeader() { + return new RolloutListHeader(permissionChecker, rolloutUIState, eventBus, rolloutManagement, targetManagement, + uiNotification, uiProperties, entityFactory, i18n, targetFilterQueryManagement); } @Override - protected Label getCountMessageLabel() { - - return null; + public AbstractGrid createGrid() { + return new RolloutListGrid(i18n, eventBus, rolloutManagement, uiNotification, rolloutUIState, permissionChecker, + targetManagement, entityFactory, uiProperties, targetFilterQueryManagement); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupListGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupListGrid.java index 0147ce025..7bfaf6349 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupListGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupListGrid.java @@ -53,7 +53,7 @@ import com.vaadin.ui.renderers.HtmlRenderer; /** * Rollout group list grid component. */ -public class RolloutGroupListGrid extends AbstractGrid { +public class RolloutGroupListGrid extends AbstractGrid { private static final long serialVersionUID = 4060904914954370524L; private static final String ROLLOUT_RENDERER_DATA = "rolloutRendererData"; @@ -98,6 +98,8 @@ public class RolloutGroupListGrid extends AbstractGrid { super(i18n, eventBus, permissionChecker); this.rolloutGroupManagement = rolloutGroupManagement; this.rolloutUIState = rolloutUIState; + + init(); } @EventBusListenerMethod(scope = EventScope.UI) @@ -131,7 +133,7 @@ public class RolloutGroupListGrid extends AbstractGrid { } @Override - protected Container createContainer() { + protected LazyQueryContainer createContainer() { final BeanQueryFactory rolloutQf = new BeanQueryFactory<>(RolloutGroupBeanQuery.class); return new LazyQueryContainer( new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, SPUILabelDefinitions.VAR_ID), rolloutQf); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListView.java index de79fd6b2..37f637630 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListView.java @@ -10,9 +10,11 @@ package org.eclipse.hawkbit.ui.rollout.rolloutgroup; import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.ui.SpPermissionChecker; -import org.eclipse.hawkbit.ui.common.grid.AbstractGridLayout; +import org.eclipse.hawkbit.ui.common.grid.AbstractGrid; +import org.eclipse.hawkbit.ui.common.grid.AbstractGridComponentLayout; import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; import org.vaadin.spring.events.EventBus.UIEventBus; import com.vaadin.ui.Label; @@ -20,10 +22,14 @@ import com.vaadin.ui.Label; /** * Groups List View. */ -public class RolloutGroupsListView extends AbstractGridLayout { +public class RolloutGroupsListView extends AbstractGridComponentLayout { private static final long serialVersionUID = 7252345838154270259L; + private final SpPermissionChecker permissionChecker; + private final RolloutUIState rolloutUIState; + private final transient RolloutGroupManagement rolloutGroupManagement; + /** * Constructor for RolloutGroupsListView * @@ -41,20 +47,21 @@ public class RolloutGroupsListView extends AbstractGridLayout { public RolloutGroupsListView(final VaadinMessageSource i18n, final UIEventBus eventBus, final RolloutGroupManagement rolloutGroupManagement, final RolloutUIState rolloutUIState, final SpPermissionChecker permissionChecker) { - super(new RolloutGroupsListHeader(eventBus, rolloutUIState, i18n), - new RolloutGroupListGrid(i18n, eventBus, rolloutGroupManagement, rolloutUIState, permissionChecker)); - - buildLayout(); + super(i18n, eventBus); + this.permissionChecker = permissionChecker; + this.rolloutUIState = rolloutUIState; + this.rolloutGroupManagement = rolloutGroupManagement; + init(); } @Override - protected boolean hasCountMessage() { - return false; + public RolloutGroupsListHeader createGridHeader() { + return new RolloutGroupsListHeader(eventBus, rolloutUIState, i18n); } @Override - protected Label getCountMessageLabel() { - return null; + public AbstractGrid createGrid() { + return new RolloutGroupListGrid(i18n, eventBus, rolloutGroupManagement, rolloutUIState, permissionChecker); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListGrid.java index c80455f0e..f70594e25 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListGrid.java @@ -42,7 +42,7 @@ import com.vaadin.server.FontAwesome; /** * Grid component with targets of rollout group. */ -public class RolloutGroupTargetsListGrid extends AbstractGrid { +public class RolloutGroupTargetsListGrid extends AbstractGrid { private static final long serialVersionUID = -2244756637458984597L; @@ -85,6 +85,8 @@ public class RolloutGroupTargetsListGrid extends AbstractGrid { final RolloutUIState rolloutUIState) { super(i18n, eventBus, null); this.rolloutUIState = rolloutUIState; + + init(); } @EventBusListenerMethod(scope = EventScope.UI) @@ -97,7 +99,7 @@ public class RolloutGroupTargetsListGrid extends AbstractGrid { } @Override - protected Container createContainer() { + protected LazyQueryContainer createContainer() { final BeanQueryFactory rolloutgrouBeanQueryFactory = new BeanQueryFactory<>( RolloutGroupTargetsBeanQuery.class); return new LazyQueryContainer( @@ -205,7 +207,7 @@ public class RolloutGroupTargetsListGrid extends AbstractGrid { } /** - * + * * Converts {@link Status} into string with status icon details. * */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListView.java index 1a4ed8396..264b08731 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListView.java @@ -8,43 +8,50 @@ */ package org.eclipse.hawkbit.ui.rollout.rolloutgrouptargets; -import org.eclipse.hawkbit.ui.common.grid.AbstractGridLayout; +import org.eclipse.hawkbit.ui.common.grid.AbstractGridComponentLayout; import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.vaadin.spring.events.EventBus.UIEventBus; +import com.vaadin.ui.AbstractOrderedLayout; import com.vaadin.ui.Label; /** * Rollout Group Targets List View. */ -public class RolloutGroupTargetsListView extends AbstractGridLayout { - +public class RolloutGroupTargetsListView extends AbstractGridComponentLayout { private static final long serialVersionUID = 26089134783467012L; - private final RolloutGroupTargetsCountLabelMessage rolloutGroupTargetsCountLabelMessage; + private final RolloutUIState rolloutUIState; public RolloutGroupTargetsListView(final UIEventBus eventBus, final VaadinMessageSource i18n, final RolloutUIState rolloutUIState) { - super(new RolloutGroupTargetsListHeader(eventBus, i18n, rolloutUIState), - new RolloutGroupTargetsListGrid(i18n, eventBus, rolloutUIState)); - - this.rolloutGroupTargetsCountLabelMessage = new RolloutGroupTargetsCountLabelMessage(rolloutUIState, - (RolloutGroupTargetsListGrid) grid, i18n, eventBus); - - buildLayout(); + super(i18n, eventBus); + this.rolloutUIState = rolloutUIState; + this.setFooterSupport(new RolloutTargetsCountFooterSupport()); + init(); } @Override - protected boolean hasCountMessage() { - - return true; + public AbstractOrderedLayout createGridHeader() { + return new RolloutGroupTargetsListHeader(eventBus, i18n, rolloutUIState); } @Override - protected Label getCountMessageLabel() { - - return rolloutGroupTargetsCountLabelMessage; + public RolloutGroupTargetsListGrid createGrid() { + return new RolloutGroupTargetsListGrid(i18n, eventBus, rolloutUIState); } + class RolloutTargetsCountFooterSupport extends AbstractFooterSupport { + private static final long serialVersionUID = 3300400541786329735L; + + @Override + protected Label getFooterMessageLabel() { + final RolloutGroupTargetsCountLabelMessage countMessageLabel = new RolloutGroupTargetsCountLabelMessage( + rolloutUIState, (RolloutGroupTargetsListGrid) getGrid(), i18n, eventBus); + countMessageLabel.setId(UIComponentIdProvider.ROLLOUT_GROUP_TARGET_LABEL); + return countMessageLabel; + } + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPDateTimeUtil.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPDateTimeUtil.java index 86071e8e2..09152f07c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPDateTimeUtil.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPDateTimeUtil.java @@ -81,6 +81,18 @@ public final class SPDateTimeUtil { return formatDate(lastQueryDate, null); } + /** + * Get formatted date with browser time zone. + * + * @param lastQueryDate + * @param datePattern + * pattern how to format the date (cp. {@code SimpleDateFormat}) + * @return String formatted date + */ + public static String getFormattedDate(final Long lastQueryDate, final String datePattern) { + return formatDate(lastQueryDate, null, datePattern); + } + /** * Get formatted date 'created at' by entity. * @@ -109,20 +121,40 @@ public final class SPDateTimeUtil { return formatDate(baseEntity.getLastModifiedAt(), StringUtils.EMPTY); } - private static String formatDate(final Long lastQueryDate, final String defaultString) { + /** + * Get formatted date 'last modified at' by entity. + * + * @param baseEntity + * the entity + * @param datePattern + * pattern how to format the date (cp. {@code SimpleDateFormat}) + * @return String formatted date + */ + public static String formatLastModifiedAt(final BaseEntity baseEntity, final String datePattern) { + if (baseEntity == null) { + return StringUtils.EMPTY; + } + return formatDate(baseEntity.getLastModifiedAt(), StringUtils.EMPTY, datePattern); + } + + private static String formatDate(final Long lastQueryDate, final String defaultString, final String datePattern) { if (lastQueryDate != null) { - final SimpleDateFormat format = new SimpleDateFormat(SPUIDefinitions.LAST_QUERY_DATE_FORMAT); + final SimpleDateFormat format = new SimpleDateFormat(datePattern); format.setTimeZone(getBrowserTimeZone()); return format.format(new Date(lastQueryDate)); } return defaultString; } + private static String formatDate(final Long lastQueryDate, final String defaultString) { + return formatDate(lastQueryDate, defaultString, SPUIDefinitions.LAST_QUERY_DATE_FORMAT); + } + /** * Creates a formatted string of a duration in format '1 year 2 months 3 * days 4 hours 5 minutes 6 seconds' zero values will be ignored in the * formatted string. - * + * * @param startMillis * the start milliseconds of the duration * @param endMillis @@ -158,34 +190,34 @@ public final class SPDateTimeUtil { /** * Enum to get the i18n key for single or plural calendar labels. - * + * * * * */ private enum CalendarI18N { /** - * + * */ YEAR("calendar.year", "calendar.years"), /** - * + * */ MONTH("calendar.month", "calendar.months"), /** - * + * */ DAY("calendar.days", "calendar.days"), /** - * + * */ HOUR("calendar.hour", "calendar.hours"), /** - * + * */ MINUTE("calendar.minute", "calendar.minutes"), /** - * + * */ SECOND("calendar.second", "calendar.seconds"); @@ -193,7 +225,7 @@ public final class SPDateTimeUtil { private final String plural; /** - * + * */ CalendarI18N(final String single, final String plural) { this.single = single; 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 0e59ecc62..823285b60 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 @@ -53,7 +53,7 @@ public final class SPUIDefinitions { /** * Action history active hidden column. This is using to generate active - * icons under active coloumn. + * icons under active column. */ public static final String ACTION_HIS_TBL_ACTIVE_HIDDEN = "Active_Hidden"; @@ -108,6 +108,11 @@ public final class SPUIDefinitions { */ public static final String ACTION_HIS_TBL_FORCED = "Forced"; + /** + * Action history action type column. + */ + public static final String ACTION_HIS_TBL_TIMEFORCED = "Time-Forced"; + /** * Action history helping constant. */ @@ -244,6 +249,22 @@ public final class SPUIDefinitions { * Text field style. */ public static final String TEXT_STYLE = "text-style"; + /** + * Show actions for a target. + */ + public static final String ACTIONS_BY_TARGET = "ActionsByTarget"; + /** + * Show action-states for a action. + */ + public static final String ACTIONSTATES_BY_ACTION = "ActionStatesByAction"; + /** + * Show messages for a action-status. + */ + public static final String MESSAGES_BY_ACTIONSTATUS = "MessagesByActionStatus"; + /** + * Key for no-message MessageProxy. + */ + public static final String NO_MSG_PROXY = "NoMessageProxy"; /** * Style to highlight row in orange color. @@ -353,6 +374,10 @@ public final class SPUIDefinitions { * Target last query date format . */ public static final String LAST_QUERY_DATE_FORMAT = "EEE MMM d HH:mm:ss z yyyy"; + /** + * Target last query date format . + */ + public static final String LAST_QUERY_DATE_FORMAT_SHORT = "MMM d HH:mm z ''yy"; /** * Item Id used in drop comparisons. */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java index c105a2b7f..037aff894 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java @@ -25,6 +25,11 @@ public final class SPUIStyleDefinitions { public static final String SP_TEXTFIELD_LAYOUT_ERROR_HIGHTLIGHT = "v-textfield-error"; + /** + * Action history message grid style. + */ + public static final String ACTION_HISTORY_MESSAGE_GRID = "action-history-message-grid"; + /** * Style for accordion tab button. */ 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 f833460ba..bad1a4b6b 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 @@ -6,6 +6,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ + package org.eclipse.hawkbit.ui.utils; /** @@ -52,17 +53,14 @@ public final class UIComponentIdProvider { * ID-Targ.PIN. */ public static final String TARGET_PIN_ICON = "target.pin.icon."; - /** * Target search text id. */ public static final String TARGET_TEXT_FIELD = "target.search.textfield"; - /** * ID for target filter search */ public static final String TARGET_FILTER_SEARCH_TEXT = "target.filter.search.text.Id"; - /** * ID for add target filter icon */ @@ -121,7 +119,6 @@ public final class UIComponentIdProvider { * ID-Dist.PIN. */ public static final String DIST_PIN_BUTTON = "dist.pin.button"; - /** * ID for distribution set tag icon */ @@ -134,27 +131,22 @@ public final class UIComponentIdProvider { * ID - soft.module.version. */ public static final String SOFT_MODULE_VERSION = "soft.module.version"; - /** * ID - soft.module.vendor. */ public static final String SOFT_MODULE_VENDOR = "soft.module.vendor"; - /** * ID - Save Assign. */ public static final String SAVE_ASSIGNMENT = "save.actions.popup.assign"; - /** * ID - Discard Assign. */ public static final String DISCARD_ASSIGNMENT = "discard.actions.popup.assign"; - /** * ID - Delete Distribution SetType Save. */ public static final String SAVE_DELETE_DIST_SET_TYPE = "save.actions.popup.delete.dist.set.type"; - /** * ID - Discard Distribution SetType. */ @@ -163,17 +155,14 @@ public final class UIComponentIdProvider { * ID Delete Software Module Type save. */ public static final String SAVE_DELETE_SW_MODULE_TYPE = "save.actions.popup.delete.sw.module.type"; - /** * ID - Discard SW Module Type. */ public static final String DISCARD_SW_MODULE_TYPE = "save.actions.popup.discard.sw.module.type"; - /** * Action history table cancel Id. */ public static final String ACTION_DETAILS_SOFT_ID = "action.details.soft.group"; - /** * Start type of rollout manual radio button */ @@ -231,24 +220,53 @@ public final class UIComponentIdProvider { */ public static final String PENDING_ACTION_BUTTON = "pending.action.button"; /** - * Action history table Id. + * Action history grid Id. */ - public static final String ACTION_HISTORY_TABLE_ID = "action.history.tableId"; - + public static final String ACTION_HISTORY_GRID_ID = "action.history.gridId"; /** - * Action history table cancel Id. + * Action history details grid Id. + */ + public static final String ACTION_HISTORY_DETAILS_GRID_ID = "action.history.details.gridId"; + /** + * Action history message grid Id. + */ + public static final String ACTION_HISTORY_MESSAGE_GRID_ID = "action.history.message.gridId"; + /** + * Action history table cancel button Id. */ public static final String ACTION_HISTORY_TABLE_CANCEL_ID = "action.history.table.action.cancel"; - /** - * Action history table force Id. + * Action history table force button Id. */ public static final String ACTION_HISTORY_TABLE_FORCE_ID = "action.history.table.action.force"; - /** - * Action history table force quit Id. + * Action history table force quit button Id. */ public static final String ACTION_HISTORY_TABLE_FORCE_QUIT_ID = "action.history.table.action.force.quit"; + /** + * Action history table forced label Id. + */ + public static final String ACTION_HISTORY_TABLE_FORCED_LABEL_ID = "action.history.table.forcedId"; + + /** + * Action history table time-forced label Id. + */ + public static final String ACTION_HISTORY_TABLE_TIMEFORCED_LABEL_ID = "action.history.table.timedforceId"; + + /** + * Action history table status label Id. + */ + public static final String ACTION_HISTORY_TABLE_STATUS_LABEL_ID = "action.history.table.statusId"; + + /** + * Action history table active-state label Id. + */ + public static final String ACTION_HISTORY_TABLE_ACTIVESTATE_LABEL_ID = "action.history.table.activeStateId"; + + /** + * Action status grid status label Id. + */ + public static final String ACTION_STATUS_GRID_STATUS_LABEL_ID = "action.status.grid.statusId"; /** * ID for option group save timeforced @@ -985,7 +1003,7 @@ public final class UIComponentIdProvider { /** * Id of the unread notification popup */ - public static final String NOTIFICATION_UNREAD_POPUP_id = "notification.unread.popup"; + public static final String NOTIFICATION_UNREAD_POPUP_ID = "notification.unread.popup"; /** * Id of the unread notification icon in the menu diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/action-history.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/action-history.scss index fc758bca3..db6b58df0 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/action-history.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/action-history.scss @@ -18,9 +18,21 @@ padding-bottom: 25px; padding-top: 5px; } - - //Color of forced label - .action-history-table-col-forced-label { - color: $hawkbit-primary-color; + + .action-history-message-grid { + .v-grid-cell { + border: none !important; + cursor: move; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + -o-text-overflow: ellipsis; + -icab-text-overflow: ellipsis; + -khtml-text-overflow: ellipsis; + -moz-text-overflow: ellipsis; + -webkit-text-overflow: ellipsis; + font-size: $v-font-size--small !important; + box-shadow: none !important; + } } } diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/common.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/common.scss index 34a990f4b..d302f4e6e 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/common.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/common.scss @@ -23,8 +23,9 @@ } .view-header-layout{ - width: 100%; + width: 100% !important; padding-right: 20px; + padding-bottom: 0.3em } .v-ui { @@ -51,10 +52,10 @@ padding-right: 20px; padding-left: 20px; min-width: 1000px; - min-height: 400px; + min-height: 500px; border: 0px; background-color: transparent; - box-shadow: none; + box-shadow: none } //View background styles @@ -65,7 +66,7 @@ background-position: bottom; background-repeat: no-repeat; border-left: 0 solid #fff; - padding-top: 80px; + padding-top: 70px; overflow: auto; } @@ -76,7 +77,7 @@ font-weight: bold; height: 32px; line-height: 50px; - padding-bottom: 5px; + padding-bottom: 0px; padding-left: 15px; padding-top: 0; position: absolute; @@ -103,7 +104,7 @@ background: $logo-image no-repeat right top; background-position: $logo-position; content: ""; - height: 80px; + height: 70px; position: absolute; top: 0; width: 100%; @@ -316,5 +317,11 @@ .form-lastrow { padding-bottom: 12px !important; } + + .text-cut { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } } diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/grid.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/grid.scss new file mode 100644 index 000000000..6c9e6b310 --- /dev/null +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/grid.scss @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations 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 + */ +@mixin grid { + .v-grid-cell.centeralign { + text-align: center; + } + + .v-grid-cell.leftalign { + text-align: left; + } + + .v-grid-cell.rightalign { + text-align: right; + } + + .v-grid-cell { + font-size: $v-font-size--small !important + } + + .v-grid-cell.frozen{ + box-shadow: none !important; + } + + .v-grid-cell.frozen + th { + border-left: $v-grid-border-size solid $widget-border-color ; + } +} \ No newline at end of file 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 88dac5700..bd48c0ad1 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 @@ -92,7 +92,7 @@ } //Pin style - when hovered on pin and when row is selected - tr.v-selected .v-button-targetStatusPinToggle:hover:after { + widget-bg .v-button-targetStatusPinToggle:hover:after { background-color: $widget-bg; color: $hawkbit-primary-color !important; } @@ -195,56 +195,97 @@ content: "\f08d"; color: $widget-bg; } - - .statusIconPending { + + // pending status label not selected + tr:not(.v-selected) .statusIconForced:after { color: $hawkbit-primary-color; } - .statusIconActive { + // pending status label selected + tr.v-selected .statusIconForced:after { + color: $widget-bg; + } + + // pending status label not selected + tr:not(.v-selected) .statusIconPending:after { + color: $hawkbit-primary-color; + } + + // pending status label selected + tr.v-selected .statusIconPending:after { + color: $widget-bg; + } + + // active status label not selected + tr:not(.v-grid-row-selected) .statusIconActive { @include valo-spinner($size:16px,$speed:1200ms); color: $hawkbit-primary-color; } - .statusIconNeutral { - color: $hawkbit-primary-color; + // active status label selected + tr.v-grid-row-selected .statusIconActive { + @include valo-spinner($size:16px,$speed:1200ms,$color: $grey-light); + color: $grey-light; } + // neutral status label not selected + tr:not(.v-selected) .statusIconNeutral:after { + color: $hawkbit-primary-color; + } + + // neutral status label selected + tr.v-selected .statusIconNeutral:after { + color: $widget-bg; + } + + // red status label not selected + tr:not(.v-selected) .borderless.statusIconRed:after { + color: $hawkbit-primary-color; + content: ''; + } + + // red status label not selected + tr.v-selected .borderless.statusIconRed:after { + color: $widget-bg; + content: ''; + } .redSpinner{ @include valo-spinner($size: $v-font-size--small,$color: $red-color); pointer-events: auto !important; } - .greySpinner{ - @include valo-spinner($size: $v-font-size--small,$color: $grey-color); - pointer-events: auto !important; - } + .greySpinner{ + @include valo-spinner($size: $v-font-size--small,$color: $grey-color); + pointer-events: auto !important; + } - .yellowSpinner{ - @include valo-spinner($size: $v-font-size--small,$color: $signal-yellow-color); - pointer-events: auto !important; - } - .blueSpinner{ - @include valo-spinner($size: $v-font-size--small,$color: $signal-light-blue-color); - pointer-events: auto !important; - } + .yellowSpinner{ + @include valo-spinner($size: $v-font-size--small,$color: $signal-yellow-color); + pointer-events: auto !important; + } + + .blueSpinner{ + @include valo-spinner($size: $v-font-size--small,$color: $signal-light-blue-color); + pointer-events: auto !important; + } // Disabled row style when distribution is incomplete - .v-table-row-incomplete-distribution { - color: $disabled-row-color-grey !important; - } + .v-table-row-incomplete-distribution { + color: $disabled-row-color-grey !important; + } - .v-link { - text-decoration: none; - padding-right: 10px; - font-weight: 300; - } + .v-link { + text-decoration: none; + padding-right: 10px; + font-weight: 300; + } - .v-tooltip{ - max-width:43em; - } + .v-tooltip{ + max-width:43em; + } - .breadcrumbPaddingLeft{ + .breadcrumbPaddingLeft{ padding-left: 3px !important; - } + } } diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/rollout.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/rollout.scss index f5bbb42d9..d14632077 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/rollout.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/rollout.scss @@ -7,97 +7,76 @@ * http://www.eclipse.org/legal/epl-v10.html */ @mixin rollout { - .rollout-option-group { - font-size: 12px; - font-weight: 400; - margin-left: 8px; - } - - .rollout-action-type-layout { - .v-caption-padding-right-style { - padding-right: 0px !important; - } - } - - .v-context-menu .v-context-menu-item-basic-icon-container { - height: 0px !important; - width: 0px !important; - } - - .v-context-menu .v-context-menu-item-basic { - background-color: #feffff !important; - border-radius: 4px; - font-family: $app-font-family; - font-size: $app-text-font-size; - font-weight: normal; - font-style: normal; - } - - .v-context-menu { - background-color: #feffff !important; - border-radius: 4px; - } - - .v-context-menu .v-context-menu-item-basic:focus, .v-context-menu .v-context-menu-item-basic-submenu:focus, .v-context-menu .v-context-menu-item-basic-open { - @include valo-gradient($color: $hawkbit-primary-color); - background-color: $hawkbit-primary-color !important; - color: #e8eef3; - height: 30px; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.05); - } - - .disable-action-type-layout { - opacity: 0.5; - } - - .action-type-padding { - padding: 0 0px !important; - } - - .rollout-caption-links { - font-weight: 400; - height: 25px; - padding: 0px 4px; - } - - .rollout-target-count-message { - color: $info-message-color-grey; - } - - .rollout-table { - .v-table-cell-wrapper { - cursor: default; - } - } - - .v-grid-cell.centeralign { - text-align: center; - } - - .v-grid-cell { - font-size: $v-font-size--small !important; - height: 34px !important; - } - - .v-grid-row { - height: 34px !important; - } - - .v-grid-cell.frozen { - box-shadow: none !important; - } - - .v-grid-cell.frozen + th { - border-left: $v-grid-border-size solid $widget-border-color; - } - - .v-button-boldhide { - text-decoration: none; - } - - - - .groups-pie-chart { + .rollout-option-group{ + font-size:12px; + font-weight:400; + margin-left:8px; + } + + .rollout-action-type-layout { + .v-caption-padding-right-style{ + padding-right:0px !important; + } + } + + + .v-context-menu .v-context-menu-item-basic-icon-container{ + height:0px !important; + width:0px !important; + } + + .v-context-menu .v-context-menu-item-basic{ + background-color: #feffff !important; + border-radius: 4px; + font-family : $app-font-family; + font-size : $app-text-font-size; + font-weight : normal; + font-style : normal; + } + + + .v-context-menu{ + background-color: #feffff !important; + border-radius: 4px; + } + + .v-context-menu .v-context-menu-item-basic:focus, .v-context-menu .v-context-menu-item-basic-submenu:focus, .v-context-menu .v-context-menu-item-basic-open { + @include valo-gradient($color: $hawkbit-primary-color); + background-color: $hawkbit-primary-color !important; + color: #e8eef3; + height: 30px; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.05); + } + + .disable-action-type-layout{ + opacity: 0.5; + } + + .action-type-padding{ + padding: 0 0px !important; + } + + .rollout-caption-links{ + font-weight: 400; + height: 25px ; + padding: 0px 4px ; + } + + .rollout-target-count-message{ + color: $info-message-color-grey; + } + + .rollout-table{ + .v-table-cell-wrapper { + cursor: default; + } + } + + .v-button-boldhide{ + text-decoration:none; + } + + .groups-pie-chart { float: right; svg { @@ -209,5 +188,6 @@ .v-slot:nth-child(16n+15) .rollout-group-count { border-left-color: #BBDE8F } .v-slot:nth-child(16n+16) .rollout-group-count { border-left-color: #7FB0A4 } } - + } + \ No newline at end of file diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss index ea5d5fbde..abc597848 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss @@ -36,7 +36,6 @@ } .v-table-resizer { - height: 37px !important; margin-left: -8px !important; } diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/hawkbittheme.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/hawkbittheme.scss index 6ab6f2e23..3da891b10 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/hawkbittheme.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/hawkbittheme.scss @@ -38,10 +38,13 @@ $v-grid-cell-horizontal-border: 0px ; $v-grid-cell-focused-border: 0px ; $v-grid-cell-padding-horizontal:6px; +$v-table-row-height: 31px; + $v-animations-enabled: true; @import '../valo/valo'; @import 'customstyles/table'; +@import 'customstyles/grid'; @import 'customstyles/filter-status'; @import 'customstyles/colorpicker'; @import 'customstyles/tags'; @@ -79,6 +82,7 @@ $v-included-components: remove($v-included-components, form); @include valo; @include generic-styles; @include table; + @include grid; @include filter-status; @include colorpicker; @include tags; diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index 3346d5851..90b77e644 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -213,7 +213,6 @@ textfield.vendor = Vendor textfield.description = Description textfield.customfiltername = Filter name textfield.value = Value -ui.version = Powered by Bosch IoT Software Provisioning prompt.target.id = Controller ID diff --git a/pom.xml b/pom.xml index aa9402976..6f78a17bb 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ true - + 0.23.0.RELEASE 3.6.2 @@ -152,7 +152,7 @@ 9.3.1 1.7.0 1.5.2 - + @@ -218,7 +218,7 @@ 1.4.1 - enforce-no-snapshots @@ -415,10 +415,10 @@ - org.sonatype.plugins nexus-staging-maven-plugin @@ -629,7 +629,7 @@ feign-jaxrs ${feign.extension.version} - + io.protostuff @@ -641,7 +641,7 @@ protostuff-runtime ${io-protostuff.version} - + cz.jirutka.rsql @@ -740,4 +740,4 @@ - \ No newline at end of file +